diff --git a/.lock b/.lock new file mode 100644 index 0000000..e69de29 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/bevy_lint/all.html b/bevy_lint/all.html new file mode 100644 index 0000000..702e905 --- /dev/null +++ b/bevy_lint/all.html @@ -0,0 +1 @@ +
Redirecting to ../../bevy_lint/static.INSERT_RESOURCE_WITH_DEFAULT.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/bundle_lints/static.BUNDLE_WITH_INCOMPLETE_TRANSFORMS.html b/bevy_lint/bundle_lints/static.BUNDLE_WITH_INCOMPLETE_TRANSFORMS.html new file mode 100644 index 0000000..6bbe610 --- /dev/null +++ b/bevy_lint/bundle_lints/static.BUNDLE_WITH_INCOMPLETE_TRANSFORMS.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../bevy_lint/static.BUNDLE_WITH_INCOMPLETE_TRANSFORMS.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/index.html b/bevy_lint/index.html new file mode 100644 index 0000000..02d8a76 --- /dev/null +++ b/bevy_lint/index.html @@ -0,0 +1,11 @@ +Transform
or GlobalTransform
, but not the other.Option
is used, that will always return None
.bevy::app::App::insert_resource
or bevy::app::App::insert_non_send_resource
+with T::default()
.Added
filters in Bevy query parameters.“Changed
filters in Bevy query parameters.“Option
queries in Bevy query parameters.Or
query filters in Bevy query parameters.With
query filters in Bevy query parameters.pub static BUNDLE_WITH_INCOMPLETE_TRANSFORMS: &Lint
What it does:
+Checks for Bundles that contain a Transform
or GlobalTransform
, but not the other.
Why is this bad?
+When creating a Hierachy of Entitys, Bevy excpects every Entity to have
+both a Transform
and GlobalTransform
.
If this is not the case then the GlobalTransform
of Children will not be updated.
Known problems: None.
+Example:
+ +#[derive(Bundle)]
+struct CustomBundle {
+ transform: Transform,
+}
Is better expressed with:
+ +#[derive(Bundle)]
+struct CustomBundle {
+ transform: Transform,
+ global_transform: GlobalTransform,
+}
pub static EMPTY_QUERY: &Lint
What it does:
+Detects empty Queries that will never return any Data.
+Also triggered when a Option
is used, that will always return None
.
Why is this bad? +These Queries will never return any Data, because no Entity will fullfill the Requirements.
+Known problems: None.
+Example:
+ +fn system(query: Query<&A, Without<A>>) {}
+
Instead do:
+ +fn system(query: Query<&A>) {}
+
pub static INSERT_RESOURCE_WITH_DEFAULT: &Lint
What it does:
+Checks for calls of bevy::app::App::insert_resource
or bevy::app::App::insert_non_send_resource
+with T::default()
.
Why is this bad?
+Readability, these can be written as .init_resource
Known problems:
+init_resource()
does not override already existing Resources.
+If you want to override a Resource with its default Values,
+you need to use insert_resource
instead.
Example:
+ +#[derive(Default, Resource)]
+struct MyResource;
+
+App::new().insert_resource(MyResource::default());
Is better expressed with:
+ +#[derive(Default, Resource)]
+struct MyResource;
+
+App::new().init_resource::<MyResource>();
pub static UNNECESSARY_ADDED: &Lint
What it does:
+Detects unnecessary Added
filters in Bevy query parameters.“
Why is this bad?
+The Changed
Filter also triggers for Component Additions.
+Thus combining them inside an Or
makes Added
unnecessary.
Known problems: None.
+Example:
+ +fn system(mut query: Query<&A, Or<(Added<B>, Changed<B>)>>) {}
+
Instead do:
+ +fn system(mut query: Query<&A, Changed<B>>) {}
+
pub static UNNECESSARY_CHANGED: &Lint
What it does:
+Detects unnecessary Changed
filters in Bevy query parameters.“
Why is this bad?
+The Changed
Filter also triggers for Component Additions.
+Thus combining them inside an Or
makes Added
unnecessary.
Known problems: None.
+Example:
+ +fn system(mut query: Query<&A, (Added<B>, Changed<B>)>) {}
+
Instead do:
+ +fn system(mut query: Query<&A, Added<B>>) {}
+
pub static UNNECESSARY_OPTION: &Lint
What it does:
+Detects unnecessary Option
queries in Bevy query parameters.
Why is this bad?
+The query will always return the Some
Variant.
Known problems: None.
+Example:
+ +fn system(query: Query<Option<&A>, With<A>>) {}
+
Instead do:
+ +fn system(query: Query<&A>) {}
+
pub static UNNECESSARY_OR: &Lint
What it does:
+Detects unnecessary Or
query filters in Bevy query parameters.
Why is this bad?
+The Or
filters can be trivialy removed, without changing the Result of the query.
Known problems: None.
+Example:
+ +fn system(query: Query<Entity, Or<(With<A>,)>>) {}
+
Instead do:
+ +fn system(query: Query<Entity, With<A>>) {}
+
pub static UNNECESSARY_WITH: &Lint
What it does:
+Detects unnecessary With
query filters in Bevy query parameters.
Why is this bad? +The Filter does not effect the Results of a query, but still wasted space.
+Known problems: None.
+Example:
+ +fn system(query: Query<&A, With<A>>) {}
+
Instead do:
+ +fn system(query: Query<&A>) {}
+
Redirecting to ../../../bevy_lint/static.EMPTY_QUERY.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/system_lints/query_lints/static.UNNECESSARY_ADDED.html b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_ADDED.html new file mode 100644 index 0000000..1fb4e9d --- /dev/null +++ b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_ADDED.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../bevy_lint/static.UNNECESSARY_ADDED.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/system_lints/query_lints/static.UNNECESSARY_CHANGED.html b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_CHANGED.html new file mode 100644 index 0000000..d8f0a35 --- /dev/null +++ b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_CHANGED.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../bevy_lint/static.UNNECESSARY_CHANGED.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/system_lints/query_lints/static.UNNECESSARY_OPTION.html b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_OPTION.html new file mode 100644 index 0000000..e08bdab --- /dev/null +++ b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_OPTION.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../bevy_lint/static.UNNECESSARY_OPTION.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/system_lints/query_lints/static.UNNECESSARY_OR.html b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_OR.html new file mode 100644 index 0000000..972b117 --- /dev/null +++ b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_OR.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../bevy_lint/static.UNNECESSARY_OR.html...
+ + + \ No newline at end of file diff --git a/bevy_lint/system_lints/query_lints/static.UNNECESSARY_WITH.html b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_WITH.html new file mode 100644 index 0000000..44b3acc --- /dev/null +++ b/bevy_lint/system_lints/query_lints/static.UNNECESSARY_WITH.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../../bevy_lint/static.UNNECESSARY_WITH.html...
+ + + \ No newline at end of file diff --git a/crates.js b/crates.js new file mode 100644 index 0000000..42d0fff --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["bevy_lint"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 0000000..ddbec73 --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +Transform
…\nWhat it does: Detects empty Queries that will never return …\nWhat it does: Checks for calls of …\nWhat it does: Detects unnecessary Added
filters in Bevy …\nWhat it does: Detects unnecessary Changed
filters in Bevy …\nWhat it does: Detects unnecessary Option
queries in Bevy …\nWhat it does: Detects unnecessary Or
query filters in Bevy …\nWhat it does: Detects unnecessary With
query filters in …")
\ No newline at end of file
diff --git a/settings.html b/settings.html
new file mode 100644
index 0000000..e891142
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +
use clippy_utils::{diagnostics::span_lint_and_then, is_diag_trait_item, source::snippet};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::{sym, Symbol};
+
+declare_lint! {
+ /// **What it does:**
+ /// Checks for calls of `bevy::app::App::insert_resource` or `bevy::app::App::insert_non_send_resource`
+ /// with `T::default()`.
+ ///
+ /// **Why is this bad?**
+ /// Readability, these can be written as .init_resource<T>(), which is simpler and more concise.
+ ///
+ /// **Known problems:**
+ /// `init_resource()` does not override already existing Resources.
+ /// If you want to override a Resource with its default Values,
+ /// you need to use `insert_resource` instead.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use bevy::app::App;
+ /// # use bevy::ecs::system::Resource;
+ /// #[derive(Default, Resource)]
+ /// struct MyResource;
+ ///
+ /// App::new().insert_resource(MyResource::default());
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// # use bevy::app::App;
+ /// # use bevy::ecs::system::Resource;
+ /// #[derive(Default, Resource)]
+ /// struct MyResource;
+ ///
+ /// App::new().init_resource::<MyResource>();
+ /// ```
+ pub INSERT_RESOURCE_WITH_DEFAULT,
+ Warn,
+ "Checks for `bevy::app::App::` on a value of type `T` with `T::default()`."
+}
+
+declare_lint_pass!(AppLintPass => [INSERT_RESOURCE_WITH_DEFAULT]);
+
+impl<'tcx> LateLintPass<'tcx> for AppLintPass {
+ fn check_expr(&mut self, cxt: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !in_external_macro(cxt.tcx.sess, expr.span) {
+ if let ExprKind::MethodCall(segment, object, func_args, expr_span) = expr.kind {
+ let is_non_send = {
+ if segment.ident.name == Symbol::intern("insert_resource") {
+ false
+ } else if segment.ident.name == Symbol::intern("insert_non_send_resource") {
+ true
+ } else {
+ return;
+ }
+ };
+
+ if_chain! {
+ if let rustc_middle::ty::TyKind::Adt(adt, _) = cxt.typeck_results().expr_ty(object).peel_refs().kind();
+ if let Some(variant) = adt.variants().iter().next();
+ if variant.ident(cxt.tcx).name == Symbol::intern("App");
+ if let ExprKind::Call(func_expr, _) = &func_args[0].kind;
+ if let ExprKind::Path(ref path) = func_expr.kind;
+ if let Some(repl_def_id) = cxt.qpath_res(path, func_expr.hir_id).opt_def_id();
+ if is_diag_trait_item(cxt, repl_def_id, sym::Default);
+ then {
+ let span = if let QPath::TypeRelative(ty, _) = path { ty.span } else { return; };
+
+ span_lint_and_then(
+ cxt,
+ INSERT_RESOURCE_WITH_DEFAULT,
+ expr_span,
+ "initializing a Resource of type `T` with `T::default()` is better expressed using `init_resource`",
+ |diag| {
+ let suggestion = {
+ if is_non_send {
+ format!("init_non_send_resource::<{}>()", snippet(cxt, span, ""))}
+ else {
+ format!("init_resource::<{}>()", snippet(cxt, span, ""))
+ }
+ };
+
+ diag.span_suggestion(
+ expr_span,
+ "consider using",
+ suggestion,
+ Applicability::MaybeIncorrect
+ );
+ }
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
pub const ADDED: &[&str; 4] = &["bevy_ecs", "query", "filter", "Added"];
+pub const CHANGED: &[&str; 4] = &["bevy_ecs", "query", "filter", "Changed"];
+pub const OR: &[&str; 4] = &["bevy_ecs", "query", "filter", "Or"];
+pub const WITH: &[&str; 4] = &["bevy_ecs", "query", "filter", "With"];
+pub const WITHOUT: &[&str; 4] = &["bevy_ecs", "query", "filter", "Without"];
+pub const QUERY: &[&str; 4] = &["bevy_ecs", "system", "query", "Query"];
+pub const SYSTEM_PARAM: &[&str; 4] = &["bevy_ecs", "system", "system_param", "SystemParam"];
+pub const BUNDLE: &[&str; 3] = &["bevy_ecs", "bundle", "Bundle"];
+
+pub const TRANSFORM: &[&str; 4] = &["bevy_transform", "components", "transform", "Transform"];
+pub const GLOBAL_TRANSFORM: &[&str; 4] = &[
+ "bevy_transform",
+ "components",
+ "global_transform",
+ "GlobalTransform",
+];
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +
use clippy_utils::{
+ diagnostics::span_lint,
+ get_trait_def_id,
+ ty::{implements_trait, match_type},
+};
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint, declare_lint_pass};
+
+use crate::{
+ bevy_paths::{BUNDLE, GLOBAL_TRANSFORM, TRANSFORM},
+ mixed_ty::MixedTy,
+};
+
+declare_lint! {
+ /// **What it does:**
+ /// Checks for Bundles that contain a `Transform` or `GlobalTransform`, but not the other.
+ ///
+ /// **Why is this bad?**
+ /// When creating a Hierachy of Entitys, Bevy excpects every Entity to have
+ /// both a `Transform` and `GlobalTransform`.
+ ///
+ /// If this is not the case then the `GlobalTransform` of Children will not be updated.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use bevy::ecs::prelude::*;
+ /// # use bevy::transform::prelude::*;
+ /// #
+ /// #[derive(Bundle)]
+ /// struct CustomBundle {
+ /// transform: Transform,
+ /// }
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// # use bevy::ecs::prelude::*;
+ /// # use bevy::transform::prelude::*;
+ /// #
+ /// #[derive(Bundle)]
+ /// struct CustomBundle {
+ /// transform: Transform,
+ /// global_transform: GlobalTransform,
+ /// }
+ /// ```
+ pub BUNDLE_WITH_INCOMPLETE_TRANSFORMS,
+ Warn,
+ "Checks for `bevy::app::App::` on a value of type `T` with `T::default()`."
+}
+
+declare_lint_pass!(BundleLintPass => [BUNDLE_WITH_INCOMPLETE_TRANSFORMS]);
+
+impl<'tcx> LateLintPass<'tcx> for BundleLintPass {
+ fn check_item(&mut self, ctx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ match item.kind {
+ ItemKind::Struct(_, _) => (),
+ _ => return,
+ };
+
+ let Some(bundle_def_id) = get_trait_def_id(ctx, BUNDLE) else {
+ return;
+ };
+
+ if !implements_trait(
+ ctx,
+ ctx.tcx.type_of(item.owner_id.def_id).skip_binder(),
+ bundle_def_id,
+ &[],
+ ) {
+ return;
+ }
+
+ let mut contains_transform = false;
+ let mut contains_global_transform = false;
+
+ for field in MixedTy::fields_from_struct_item(ctx, item).iter().flatten() {
+ if match_type(ctx, field.middle, TRANSFORM) {
+ contains_transform = true;
+ } else if match_type(ctx, field.middle, GLOBAL_TRANSFORM) {
+ contains_global_transform = true;
+ } else {
+ continue;
+ }
+
+ if contains_transform && contains_global_transform {
+ return;
+ }
+ }
+
+ let msg = match (contains_transform, contains_global_transform) {
+ (true, false) => {
+ "This Bundle contains the \"GlobalTransform\" Component, but is missing the \"Transform\" Component."
+ }
+ (false, true) => {
+ "This Bundle contains the \"Transform\" Component, but is missing the \"GlobalTransform\" Component."
+ }
+ _ => return,
+ };
+
+ span_lint(ctx, BUNDLE_WITH_INCOMPLETE_TRANSFORMS, item.span, msg);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +
#![feature(rustc_private)]
+//#![warn(unused_extern_crates)]
+#![recursion_limit = "1000"]
+
+dylint_linting::dylint_library!();
+
+extern crate rustc_ast;
+//extern crate rustc_ast_pretty;
+//extern crate rustc_attr;
+//extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+//extern crate rustc_hir_pretty;
+//extern crate rustc_index;
+//extern crate rustc_infer;
+//extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+//extern crate rustc_mir;
+//extern crate rustc_parse;
+//extern crate rustc_parse_format;
+extern crate rustc_session;
+extern crate rustc_span;
+//extern crate rustc_target;
+//extern crate rustc_trait_selection;
+//extern crate rustc_typeck;
+
+mod app_lints;
+mod bevy_paths;
+mod bundle_lints;
+mod mixed_ty;
+mod system_lints;
+
+pub use app_lints::INSERT_RESOURCE_WITH_DEFAULT;
+pub use bundle_lints::BUNDLE_WITH_INCOMPLETE_TRANSFORMS;
+pub use system_lints::query_lints::{
+ EMPTY_QUERY, UNNECESSARY_ADDED, UNNECESSARY_CHANGED, UNNECESSARY_OPTION, UNNECESSARY_OR,
+ UNNECESSARY_WITH,
+};
+
+#[no_mangle]
+#[doc(hidden)]
+pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) {
+ lint_store.register_lints(&[
+ INSERT_RESOURCE_WITH_DEFAULT,
+ BUNDLE_WITH_INCOMPLETE_TRANSFORMS,
+ EMPTY_QUERY,
+ UNNECESSARY_ADDED,
+ UNNECESSARY_CHANGED,
+ UNNECESSARY_OPTION,
+ UNNECESSARY_OR,
+ UNNECESSARY_WITH,
+ ]);
+ lint_store.register_late_pass(|_| Box::new(app_lints::AppLintPass));
+ lint_store.register_late_pass(|_| Box::new(bundle_lints::BundleLintPass));
+ lint_store.register_late_pass(|_| Box::new(system_lints::SystemLintPass));
+}
+
+#[test]
+fn ui() {
+ dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME"));
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +
use either::Either;
+use rustc_lint::LateContext;
+use rustc_span::Span;
+
+use crate::bevy_paths;
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct MixedTy<'tcx> {
+ pub(crate) hir: Either<&'tcx rustc_hir::Ty<'tcx>, Span>,
+ pub(crate) middle: rustc_middle::ty::Ty<'tcx>,
+}
+
+impl<'tcx> MixedTy<'tcx> {
+ pub(crate) fn fields_from_struct_item(
+ ctx: &LateContext<'tcx>,
+ item: &rustc_hir::Item<'tcx>,
+ ) -> Option<Vec<Self>> {
+ match item.kind {
+ rustc_hir::ItemKind::Struct(
+ rustc_hir::VariantData::Struct {
+ fields: hir_fields, ..
+ }
+ | rustc_hir::VariantData::Tuple(hir_fields, _, _),
+ _,
+ ) => {
+ let middle: rustc_middle::ty::Ty =
+ ctx.tcx.type_of(item.owner_id.def_id).skip_binder();
+
+ let middle_fields = match middle.kind() {
+ rustc_middle::ty::TyKind::Adt(def, _) => {
+ if !def.is_struct() {
+ return None;
+ }
+ def.variants()
+ .iter()
+ .flat_map(|variant| &variant.fields)
+ .map(|field_def| ctx.tcx.type_of(field_def.did))
+ }
+ _ => return None,
+ };
+
+ let vec = hir_fields
+ .iter()
+ .map(|hir_field| hir_field.ty)
+ .zip(middle_fields)
+ .map(|item| Self {
+ hir: Either::Left(item.0),
+ middle: item.1.skip_binder(),
+ })
+ .collect();
+ Some(vec)
+ }
+ _ => None,
+ }
+ }
+
+ pub(crate) fn fn_inputs_from_fn_item(
+ ctx: &LateContext<'tcx>,
+ item: &rustc_hir::Item<'tcx>,
+ ) -> Option<Vec<Self>> {
+ match item.kind {
+ rustc_hir::ItemKind::Fn(rustc_hir::FnSig { decl, .. }, _, _) => {
+ // rust-analyzer doesn't find `type_of`.
+ // The return type is manually specified to still get autocompletion.
+ let middle: rustc_middle::ty::Ty =
+ ctx.tcx.type_of(item.owner_id.def_id).skip_binder();
+
+ let inputs = middle.fn_sig(ctx.tcx).skip_binder().inputs();
+
+ let vec = decl
+ .inputs
+ .iter()
+ .zip(inputs.iter())
+ .map(|item| Self {
+ hir: Either::Left(item.0),
+ middle: *item.1,
+ })
+ .collect();
+
+ Some(vec)
+ }
+ _ => None,
+ }
+ }
+
+ pub(crate) fn fn_inputs_from_impl_fn_item(
+ ctx: &LateContext<'tcx>,
+ item: &rustc_hir::ImplItem<'tcx>,
+ ) -> Option<Vec<Self>> {
+ match item.kind {
+ rustc_hir::ImplItemKind::Fn(rustc_hir::FnSig { decl, .. }, _) => {
+ // rust-analyzer doesn't find `type_of`.
+ // The return type is manually specified to still get autocompletion.
+ let middle: rustc_middle::ty::Ty =
+ ctx.tcx.type_of(item.owner_id.def_id).skip_binder();
+
+ let inputs = middle.fn_sig(ctx.tcx).skip_binder().inputs();
+
+ let vec = decl
+ .inputs
+ .iter()
+ .zip(inputs.iter())
+ .map(|item| Self {
+ hir: Either::Left(item.0),
+ middle: *item.1,
+ })
+ .collect();
+
+ Some(vec)
+ }
+ _ => None,
+ }
+ }
+
+ pub(crate) fn fn_inputs_from_trait_fn_item(
+ ctx: &LateContext<'tcx>,
+ item: &rustc_hir::TraitItem<'tcx>,
+ ) -> Option<Vec<Self>> {
+ match item.kind {
+ rustc_hir::TraitItemKind::Fn(rustc_hir::FnSig { decl, .. }, _) => {
+ // rust-analyzer doesn't find `type_of`.
+ // The return type is manually specified to still get autocompletion.
+ let middle: rustc_middle::ty::Ty =
+ ctx.tcx.type_of(item.owner_id.def_id).skip_binder();
+
+ let inputs = middle.fn_sig(ctx.tcx).skip_binder().inputs();
+
+ let vec = decl
+ .inputs
+ .iter()
+ .zip(inputs.iter())
+ .map(|item| Self {
+ hir: Either::Left(item.0),
+ middle: *item.1,
+ })
+ .collect();
+
+ Some(vec)
+ }
+ _ => None,
+ }
+ }
+
+ pub(crate) fn extract_tuple_types(&self) -> Option<Vec<Self>> {
+ let middle_types: Vec<_> = match self.middle.kind() {
+ rustc_middle::ty::TyKind::Tuple(_) => self.middle.tuple_fields().iter().collect(),
+ _ => return None,
+ };
+
+ let hir_data: Vec<_> = match self.hir {
+ Either::Left(ty) => match ty.kind {
+ rustc_hir::TyKind::Tup(hir_types) => hir_types.iter().map(Either::Left).collect(),
+ _ => return None,
+ },
+ Either::Right(_) => std::iter::repeat(self.hir)
+ .take(middle_types.len())
+ .collect(),
+ };
+
+ assert_eq!(hir_data.len(), middle_types.len());
+
+ let vec = hir_data
+ .iter()
+ .zip(middle_types)
+ .map(|item| Self {
+ hir: *item.0,
+ middle: item.1,
+ })
+ .collect();
+ Some(vec)
+ }
+
+ pub(crate) fn extract_generics_from_struct(&self) -> Option<Vec<Self>> {
+ let middle_generics: Vec<_> = match self.middle.kind() {
+ rustc_middle::ty::TyKind::Adt(_, generics) => generics
+ .iter()
+ .filter_map(|generic| {
+ if let rustc_middle::ty::GenericArgKind::Type(ty) = generic.unpack() {
+ Some(ty)
+ } else {
+ None
+ }
+ })
+ .collect(),
+ _ => return None,
+ };
+
+ let hir_generics: Vec<_> = match self.hir {
+ Either::Left(ty) => match &ty.kind {
+ rustc_hir::TyKind::Path(q_path) => clippy_utils::qpath_generic_tys(q_path)
+ .map(Either::Left)
+ .collect(),
+ _ => return None,
+ },
+ Either::Right(_) => std::iter::repeat(self.hir)
+ .take(middle_generics.len())
+ .collect(),
+ };
+
+ assert_eq!(hir_generics.len(), middle_generics.len());
+
+ let vec = hir_generics
+ .iter()
+ .zip(middle_generics.iter())
+ .map(|item| Self {
+ hir: *item.0,
+ middle: *item.1,
+ })
+ .collect();
+
+ Some(vec)
+ }
+
+ pub(crate) fn strip_reference(&self) -> Option<(Self, rustc_ast::Mutability)> {
+ let hir_data = match self.hir {
+ Either::Left(hir_ty) => match hir_ty.kind {
+ rustc_hir::TyKind::Ref(_, rustc_hir::MutTy { ty, .. }) => Either::Left(ty),
+ _ => return None,
+ },
+ Either::Right(_) => self.hir,
+ };
+
+ let middle_data = match self.middle.kind() {
+ rustc_middle::ty::TyKind::Ref(_, ty, mutbl) => (ty, mutbl),
+ _ => return None,
+ };
+
+ Some((
+ Self {
+ hir: hir_data,
+ middle: *middle_data.0,
+ },
+ *middle_data.1,
+ ))
+ }
+
+ pub(crate) fn span(&self) -> Span {
+ match self.hir {
+ Either::Left(hir) => hir.span,
+ Either::Right(span) => span,
+ }
+ }
+
+ pub(crate) fn get_generics_of_query(
+ &self,
+ ctx: &LateContext<'tcx>,
+ ) -> Option<(Self, Option<Self>)> {
+ if clippy_utils::ty::match_type(ctx, self.middle, bevy_paths::QUERY) {
+ let hir = match self.hir {
+ either::Either::Left(ty) => match get_generics_of_query_hir(ty) {
+ Some(generics) => Either::Left(generics),
+ None => return None,
+ },
+ either::Either::Right(span) => Either::Right(span),
+ };
+
+ if let Some(middle) = get_generics_of_query_middle(self.middle) {
+ let world = Self {
+ hir: match hir {
+ Either::Left((hir_ty, _)) => Either::Left(hir_ty),
+ Either::Right(span) => Either::Right(span),
+ },
+ middle: middle.0,
+ };
+
+ let filter = match hir {
+ Either::Left(hir_ty) => hir_ty.1.and_then(|hir| {
+ middle.1.map(|middle| Self {
+ hir: Either::Left(hir),
+ middle,
+ })
+ }),
+ Either::Right(span) => middle.1.map(|middle| Self {
+ hir: Either::Right(span),
+ middle,
+ }),
+ };
+
+ return Some((world, filter));
+ }
+ }
+ None
+ }
+}
+
+fn get_generics_of_query_hir<'tcx>(
+ query: &'tcx rustc_hir::Ty,
+) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Option<&'tcx rustc_hir::Ty<'tcx>>)> {
+ if let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) = query.kind {
+ if let Some(segment) = path.segments.iter().last() {
+ if let Some(generic_args) = segment.args {
+ if let Some(rustc_hir::GenericArg::Type(world)) = &generic_args.args.get(2) {
+ if let Some(rustc_hir::GenericArg::Type(filter)) = &generic_args.args.get(3) {
+ return Some((world, Some(filter)));
+ }
+
+ return Some((world, None));
+ }
+ }
+ }
+ }
+
+ None
+}
+
+fn get_generics_of_query_middle(
+ query: rustc_middle::ty::Ty,
+) -> Option<(rustc_middle::ty::Ty, Option<rustc_middle::ty::Ty>)> {
+ if let rustc_middle::ty::TyKind::Adt(_, generics) = query.kind() {
+ if let Some(world) = generics.get(2).map(|generic| generic.expect_ty()) {
+ let filter = generics.get(3).map(|generic| generic.expect_ty());
+ return Some((world, filter));
+ }
+ }
+
+ None
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +
use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::declare_lint_pass;
+use rustc_span::Span;
+
+mod model;
+pub mod query_lints;
+
+use self::{
+ model::{FilterQuery, Query, SystemParamType, WorldQuery},
+ query_lints::{
+ lint_query, EMPTY_QUERY, UNNECESSARY_ADDED, UNNECESSARY_CHANGED, UNNECESSARY_OPTION,
+ UNNECESSARY_OR, UNNECESSARY_WITH,
+ },
+};
+use super::{bevy_paths, mixed_ty::MixedTy};
+
+declare_lint_pass!(SystemLintPass =>
+ [
+ EMPTY_QUERY,
+ UNNECESSARY_ADDED,
+ UNNECESSARY_CHANGED,
+ UNNECESSARY_OPTION,
+ UNNECESSARY_OR,
+ UNNECESSARY_WITH
+ ]
+);
+
+impl<'tcx> LateLintPass<'tcx> for SystemLintPass {
+ // A list of things you might check can be found here:
+ // https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html
+
+ fn check_item(&mut self, ctx: &LateContext<'tcx>, item: &'tcx rustc_hir::Item<'tcx>) {
+ if let Some(system_params) = match &item.kind {
+ rustc_hir::ItemKind::Fn(_, _, _) => MixedTy::fn_inputs_from_fn_item(ctx, item),
+ rustc_hir::ItemKind::Struct(rustc_hir::VariantData::Struct { .. }, _) => {
+ let Some(system_param_def_id) =
+ clippy_utils::get_trait_def_id(ctx, bevy_paths::SYSTEM_PARAM)
+ else {
+ return;
+ };
+
+ if !clippy_utils::ty::implements_trait(
+ ctx,
+ ctx.tcx.type_of(item.owner_id.def_id).skip_binder(),
+ system_param_def_id,
+ &[],
+ ) {
+ return;
+ }
+
+ MixedTy::fields_from_struct_item(ctx, item)
+ }
+ _ => return,
+ } {
+ lint_function_signature(ctx, &system_params);
+ }
+ }
+
+ fn check_trait_item(
+ &mut self,
+ ctx: &LateContext<'tcx>,
+ item: &'tcx rustc_hir::TraitItem<'tcx>,
+ ) {
+ match item.kind {
+ rustc_hir::TraitItemKind::Fn(_, _) => (),
+ _ => return,
+ }
+
+ if let Some(system_params) = MixedTy::fn_inputs_from_trait_fn_item(ctx, item) {
+ lint_function_signature(ctx, &system_params);
+ }
+ }
+
+ fn check_impl_item(&mut self, ctx: &LateContext<'tcx>, item: &'tcx rustc_hir::ImplItem<'tcx>) {
+ match item.kind {
+ rustc_hir::ImplItemKind::Fn(_, _) => (),
+ _ => return,
+ }
+
+ if let Some(system_params) = MixedTy::fn_inputs_from_impl_fn_item(ctx, item) {
+ lint_function_signature(ctx, &system_params);
+ }
+ }
+}
+
+fn lint_function_signature<'tcx>(ctx: &LateContext<'tcx>, inputs: &[MixedTy<'tcx>]) {
+ let system_params = inputs
+ .iter()
+ .filter_map(|mixed_ty| recursively_resolve_system_param(ctx, mixed_ty))
+ .map(|mut system_param| {
+ system_param.remove_substitutions();
+ system_param
+ });
+
+ for system_param in system_params {
+ recursively_lint_system_param(ctx, system_param);
+ }
+}
+
+fn recursively_lint_system_param(ctx: &LateContext, system_param: SystemParamType) {
+ match system_param {
+ SystemParamType::Tuple(system_params) => {
+ for system_param in system_params {
+ recursively_lint_system_param(ctx, system_param);
+ }
+ }
+ SystemParamType::Query(query) => {
+ lint_query(ctx, query);
+ }
+ }
+}
+
+fn recursively_resolve_system_param<'tcx>(
+ ctx: &LateContext<'tcx>,
+ ty: &MixedTy<'tcx>,
+) -> Option<SystemParamType<'tcx>> {
+ if let Some(types) = ty.extract_tuple_types() {
+ let vec = types
+ .iter()
+ .filter_map(|ty| recursively_resolve_system_param(ctx, ty))
+ .collect();
+
+ Some(SystemParamType::Tuple(vec))
+ } else if clippy_utils::ty::match_type(ctx, ty.middle, bevy_paths::QUERY) {
+ resolve_query(ctx, ty).map(SystemParamType::Query)
+ } else {
+ None
+ }
+}
+
+fn resolve_query<'tcx>(ctx: &LateContext<'tcx>, ty: &MixedTy<'tcx>) -> Option<Query<'tcx>> {
+ ty.get_generics_of_query(ctx).map(|(world, filter)| {
+ let resolved_world = recursively_resolve_world_query(ctx, &world)
+ .unwrap_or_else(|| WorldQuery::Tuple(Vec::new(), ty.span()));
+ let resolved_filter = filter
+ .and_then(|filter| recursively_resolve_filter_query(ctx, &filter))
+ .map_or_else(
+ || FilterQuery::Tuple(Vec::new()),
+ |resolved_filter| resolved_filter,
+ );
+
+ Query {
+ world_query: resolved_world,
+ filter_query: resolved_filter,
+ span: ty.span(),
+ }
+ })
+}
+
+fn recursively_resolve_world_query<'tcx>(
+ ctx: &LateContext<'tcx>,
+ world: &MixedTy<'tcx>,
+) -> Option<WorldQuery<'tcx>> {
+ match world.middle.kind() {
+ rustc_middle::ty::TyKind::Tuple(_) => world.extract_tuple_types().map(|types| {
+ let vec = types
+ .iter()
+ .filter_map(|ty| recursively_resolve_world_query(ctx, ty))
+ .collect();
+
+ WorldQuery::Tuple(vec, world.span())
+ }),
+ rustc_middle::ty::TyKind::Adt(_, _) => {
+ if clippy_utils::ty::match_type(ctx, world.middle, &["core", "option", "Option"]) {
+ let generics = world.extract_generics_from_struct().unwrap();
+ assert_eq!(generics.len(), 1);
+ recursively_resolve_world_query(ctx, &generics[0]).map(|world_query| {
+ let span = *world_query.span();
+ WorldQuery::Option((Box::new(world_query), span), world.span())
+ })
+ } else {
+ None
+ }
+ }
+ rustc_middle::ty::TyKind::Ref(_, _, _) => world
+ .strip_reference()
+ .map(|(ty, mutbl)| WorldQuery::Data(*ty.middle.kind(), mutbl, world.span())),
+ _ => None,
+ }
+}
+
+fn recursively_resolve_filter_query<'tcx>(
+ ctx: &LateContext<'tcx>,
+ filter: &MixedTy<'tcx>,
+) -> Option<FilterQuery<'tcx>> {
+ if let Some(types) = filter.extract_tuple_types() {
+ let vec = types
+ .iter()
+ .filter_map(|ty| recursively_resolve_filter_query(ctx, ty))
+ .collect();
+
+ Some(FilterQuery::Tuple(vec))
+ } else if clippy_utils::ty::match_type(ctx, filter.middle, bevy_paths::OR) {
+ let generics = filter.extract_generics_from_struct().unwrap();
+ assert_eq!(generics.len(), 1);
+
+ let vec = generics[0]
+ .extract_tuple_types()
+ .unwrap()
+ .iter()
+ .filter_map(|ty| recursively_resolve_filter_query(ctx, ty))
+ .collect();
+
+ Some(FilterQuery::Or(vec, filter.span()))
+ } else {
+ let constructor: Option<fn(rustc_middle::ty::TyKind<'tcx>, Span) -> FilterQuery<'tcx>> = {
+ if clippy_utils::ty::match_type(ctx, filter.middle, bevy_paths::ADDED) {
+ Some(FilterQuery::Added)
+ } else if clippy_utils::ty::match_type(ctx, filter.middle, bevy_paths::CHANGED) {
+ Some(FilterQuery::Changed)
+ } else if clippy_utils::ty::match_type(ctx, filter.middle, bevy_paths::WITH) {
+ Some(FilterQuery::With)
+ } else if clippy_utils::ty::match_type(ctx, filter.middle, bevy_paths::WITHOUT) {
+ Some(FilterQuery::Without)
+ } else {
+ None
+ }
+ };
+
+ constructor.map(|constructor| {
+ let generics = filter.extract_generics_from_struct().unwrap();
+ assert_eq!(generics.len(), 1);
+
+ let filter_query = constructor(*generics[0].middle.kind(), filter.span());
+
+ filter_query
+ })
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +
use rustc_span::{Span, Symbol};
+
+#[derive(Debug, Clone)]
+pub enum SystemParamType<'tcx> {
+ //Alias(Vec<SystemParamType>, Span),
+ //Derive(Vec<SystemParamType>, Span),
+ Tuple(Vec<SystemParamType<'tcx>>),
+ Query(Query<'tcx>),
+ //QuerySet(Vec<(Query, Span)>, Span),
+ //Resource(Resource),
+ //Option(Resource),
+ //Commands,
+ //Local,
+ //EventReader,
+ //EventWriter,
+}
+
+impl<'tcx> SystemParamType<'tcx> {
+ pub fn remove_substitutions(&mut self) {
+ match self {
+ SystemParamType::Tuple(system_params) => {
+ for system_param in system_params {
+ system_param.remove_substitutions();
+ }
+ }
+ SystemParamType::Query(query) => query.remove_substitutions(),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Query<'tcx> {
+ pub world_query: WorldQuery<'tcx>,
+ pub filter_query: FilterQuery<'tcx>,
+ pub span: Span,
+}
+
+impl<'tcx> Query<'tcx> {
+ pub fn remove_substitutions(&mut self) {
+ self.world_query.remove_substitutions();
+ self.filter_query.remove_substitutions();
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum WorldQuery<'tcx> {
+ Tuple(Vec<WorldQuery<'tcx>>, Span),
+ Data(rustc_middle::ty::TyKind<'tcx>, rustc_ast::Mutability, Span),
+ Option((Box<WorldQuery<'tcx>>, Span), Span),
+}
+
+impl<'tcx> WorldQuery<'tcx> {
+ pub fn remove_substitutions(&mut self) {
+ match self {
+ WorldQuery::Tuple(world_querys, _) => {
+ for world_query in world_querys {
+ world_query.remove_substitutions();
+ }
+ }
+ WorldQuery::Data(kind, _, _) => {
+ if let rustc_middle::ty::TyKind::Alias(_, projection) = kind {
+ let target = projection.args.first().unwrap().expect_ty().kind();
+
+ match target {
+ rustc_middle::ty::TyKind::Param(param)
+ if param.name == Symbol::intern("Self") =>
+ { /* Do nothing here, to avoid turning associative types into self. */ }
+ _ => *kind = *target,
+ }
+ }
+ }
+ WorldQuery::Option(world_query, _) => world_query.0.remove_substitutions(),
+ }
+ }
+
+ pub fn span(&self) -> &Span {
+ match self {
+ WorldQuery::Tuple(_, span)
+ | WorldQuery::Data(_, _, span)
+ | WorldQuery::Option(_, span) => span,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum FilterQuery<'tcx> {
+ Tuple(Vec<FilterQuery<'tcx>>),
+ Or(Vec<FilterQuery<'tcx>>, Span),
+ With(rustc_middle::ty::TyKind<'tcx>, Span),
+ Without(rustc_middle::ty::TyKind<'tcx>, Span),
+ Added(rustc_middle::ty::TyKind<'tcx>, Span),
+ Changed(rustc_middle::ty::TyKind<'tcx>, Span),
+}
+
+impl<'tcx> FilterQuery<'tcx> {
+ pub fn remove_substitutions(&mut self) {
+ match self {
+ FilterQuery::Tuple(filter_querys) | FilterQuery::Or(filter_querys, _) => {
+ for filter_query in filter_querys {
+ filter_query.remove_substitutions();
+ }
+ }
+ FilterQuery::With(kind, _)
+ | FilterQuery::Without(kind, _)
+ | FilterQuery::Added(kind, _)
+ | FilterQuery::Changed(kind, _) => {
+ if let rustc_middle::ty::TyKind::Alias(_, projection) = kind {
+ let target = projection.args.first().unwrap().expect_ty().kind();
+
+ match target {
+ rustc_middle::ty::TyKind::Param(param)
+ if param.name == Symbol::intern("Self") =>
+ { /* Do nothing here, to avoid turning associative types into self. */ }
+ _ => *kind = *target,
+ }
+ }
+ }
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +
use clippy_utils::diagnostics;
+use itertools::Itertools;
+use rustc_ast::Mutability;
+use rustc_lint::LateContext;
+use rustc_middle::ty::TyKind;
+use rustc_session::declare_lint;
+use rustc_span::Span;
+
+use super::model::{FilterQuery, Query, WorldQuery};
+
+declare_lint! {
+ /// **What it does:**
+ /// Detects unnecessary `With` query filters in Bevy query parameters.
+ ///
+ /// **Why is this bad?**
+ /// The Filter does not effect the Results of a query, but still wasted space.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<&A, With<A>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ /// Instead do:
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<&A>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ pub UNNECESSARY_WITH,
+ Warn,
+ "Detects unnecessary `With` query filters in Bevy query parameters."
+}
+
+declare_lint! {
+ /// **What it does:**
+ /// Detects unnecessary `Option` queries in Bevy query parameters.
+ ///
+ /// **Why is this bad?**
+ /// The query will always return the `Some` Variant.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<Option<&A>, With<A>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ /// Instead do:
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<&A>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ pub UNNECESSARY_OPTION,
+ Warn,
+ "Detects unnecessary `Option` queries in Bevy query parameters."
+}
+
+declare_lint! {
+ /// **What it does:**
+ /// Detects unnecessary `Or` query filters in Bevy query parameters.
+ ///
+ /// **Why is this bad?**
+ /// The `Or` filters can be trivialy removed, without changing the Result of the query.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<Entity, Or<(With<A>,)>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ /// Instead do:
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<Entity, With<A>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ pub UNNECESSARY_OR,
+ Warn,
+ "Detects unnecessary `Or` filters in Bevy query parameters."
+}
+
+declare_lint! {
+ /// **What it does:**
+ /// Detects empty Queries that will never return any Data.
+ /// Also triggered when a `Option` is used, that will always return `None`.
+ ///
+ /// **Why is this bad?**
+ /// These Queries will never return any Data, because no Entity will fullfill the Requirements.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<&A, Without<A>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ /// Instead do:
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// #
+ /// fn system(query: Query<&A>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ pub EMPTY_QUERY,
+ Warn,
+ "Detects empty Queries."
+}
+
+declare_lint! {
+ /// **What it does:**
+ /// Detects unnecessary `Added` filters in Bevy query parameters."
+ ///
+ /// **Why is this bad?**
+ /// The `Changed` Filter also triggers for Component Additions.
+ /// Thus combining them inside an `Or` makes `Added` unnecessary.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// # #[derive(Component)]
+ /// # struct B;
+ /// #
+ /// fn system(mut query: Query<&A, Or<(Added<B>, Changed<B>)>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ /// Instead do:
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// # #[derive(Component)]
+ /// # struct B;
+ /// #
+ /// fn system(mut query: Query<&A, Changed<B>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ pub UNNECESSARY_ADDED,
+ Warn,
+ "Detects unnecessary `Added` filters in Bevy query parameters."
+}
+
+declare_lint! {
+ /// **What it does:**
+ /// Detects unnecessary `Changed` filters in Bevy query parameters."
+ ///
+ /// **Why is this bad?**
+ /// The `Changed` Filter also triggers for Component Additions.
+ /// Thus combining them inside an `Or` makes `Added` unnecessary.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// # #[derive(Component)]
+ /// # struct B;
+ /// #
+ /// fn system(mut query: Query<&A, (Added<B>, Changed<B>)>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ /// Instead do:
+ /// ```rust
+ /// # use bevy::ecs::{prelude::*, system::{assert_is_system, SystemParam}};
+ /// #
+ /// # #[derive(Component)]
+ /// # struct A;
+ /// # #[derive(Component)]
+ /// # struct B;
+ /// #
+ /// fn system(mut query: Query<&A, Added<B>>) {}
+ ///
+ /// # assert_is_system(system);
+ /// ```
+ pub UNNECESSARY_CHANGED,
+ Warn,
+ "Detects unnecessary `Changed` filters in Bevy query parameters."
+}
+
+#[allow(clippy::type_complexity)]
+#[derive(Debug, Default, Clone)]
+struct QueryData<'tcx> {
+ data: Vec<(TyKind<'tcx>, Vec<(Mutability, Span)>)>,
+ option: Vec<(QueryData<'tcx>, Span)>,
+ with: Vec<(TyKind<'tcx>, Vec<Span>)>,
+ without: Vec<(TyKind<'tcx>, Vec<Span>)>,
+ added: Vec<(TyKind<'tcx>, Vec<Span>)>,
+ changed: Vec<(TyKind<'tcx>, Vec<Span>)>,
+ or: Vec<(Vec<QueryData<'tcx>>, Span)>,
+ meta: QueryDataMeta,
+}
+
+impl<'tcx> QueryData<'tcx> {
+ fn fill_with_world_query(&mut self, world_query: &WorldQuery<'tcx>) {
+ match world_query {
+ WorldQuery::Tuple(world_querys, _) => {
+ for world_query in world_querys {
+ self.fill_with_world_query(world_query);
+ }
+ }
+ WorldQuery::Data(ty_kind, mutbl, span) => {
+ for (kind, vec) in &mut self.data {
+ if ty_kind == kind {
+ vec.push((*mutbl, *span));
+ return;
+ }
+ }
+
+ self.data.push((*ty_kind, vec![(*mutbl, *span)]));
+ }
+ WorldQuery::Option(world_query, span) => {
+ let mut world = QueryData {
+ meta: QueryDataMeta::Option,
+ ..Default::default()
+ };
+ world.fill_with_world_query(&world_query.0);
+ self.option.push((world, *span));
+ }
+ }
+ }
+
+ fn fill_with_filter_query(&mut self, filter_query: &FilterQuery<'tcx>) {
+ match filter_query {
+ FilterQuery::Tuple(filter_querys) => {
+ for filter_query in filter_querys {
+ self.fill_with_filter_query(filter_query);
+ }
+ }
+ FilterQuery::Or(filter_querys, span) => {
+ let mut vec = Vec::new();
+ for filter_query in filter_querys {
+ match filter_query {
+ FilterQuery::Or(filter_querys, _) => {
+ // TODO: Lint here for nested or?
+ for filter_query in filter_querys {
+ let mut data = QueryData::default();
+ data.fill_with_filter_query(filter_query);
+ vec.push(data);
+ }
+ }
+ FilterQuery::Tuple(_)
+ | FilterQuery::With(_, _)
+ | FilterQuery::Without(_, _)
+ | FilterQuery::Added(_, _)
+ | FilterQuery::Changed(_, _) => {
+ let mut data = QueryData::default();
+ data.fill_with_filter_query(filter_query);
+ vec.push(data);
+ }
+ }
+ }
+ self.or.push((vec, *span));
+ }
+ FilterQuery::With(ty_kind, span) => {
+ for (kind, vec) in &mut self.with {
+ if ty_kind == kind {
+ vec.push(*span);
+ return;
+ }
+ }
+
+ self.with.push((*ty_kind, vec![*span]));
+ }
+ FilterQuery::Without(ty_kind, span) => {
+ for (kind, vec) in &mut self.without {
+ if ty_kind == kind {
+ vec.push(*span);
+ return;
+ }
+ }
+
+ self.without.push((*ty_kind, vec![*span]));
+ }
+ FilterQuery::Added(ty_kind, span) => {
+ for (kind, vec) in &mut self.added {
+ if ty_kind == kind {
+ vec.push(*span);
+ return;
+ }
+ }
+
+ self.added.push((*ty_kind, vec![*span]));
+ }
+ FilterQuery::Changed(ty_kind, span) => {
+ for (kind, vec) in &mut self.changed {
+ if ty_kind == kind {
+ vec.push(*span);
+ return;
+ }
+ }
+
+ self.changed.push((*ty_kind, vec![*span]));
+ }
+ }
+ }
+
+ fn lint_query_data(&self, ctx: &LateContext, span: &Span, facts: &[&QueryData<'tcx>]) {
+ let mut new_facts = Vec::from(facts);
+ new_facts.push(self);
+ self.check_for_unnecessary_or(ctx, &new_facts);
+
+ for (ty_kind, data) in self.with.iter() {
+ if contains_ty_kind(&self.data, ty_kind)
+ || contains_ty_kind(&self.added, ty_kind)
+ || contains_ty_kind(&self.changed, ty_kind)
+ {
+ for inst in data {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_WITH,
+ *inst,
+ "Unnecessary `With` Filter",
+ );
+ }
+ }
+ }
+
+ for (ty_kind, data) in self.changed.iter() {
+ if contains_ty_kind(&self.added, ty_kind) {
+ for inst in data {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_CHANGED,
+ *inst,
+ "Unnecessary `Changed` Filter",
+ );
+ }
+ }
+ }
+
+ if QueryDataMeta::Default == self.meta {
+ let mut contradiction = false;
+
+ for (ty_kind, data) in self.without.iter() {
+ if contains_ty_kind(&self.data, ty_kind)
+ || contains_ty_kind(&self.added, ty_kind)
+ || contains_ty_kind(&self.changed, ty_kind)
+ || contains_ty_kind(&self.with, ty_kind)
+ {
+ contradiction = true;
+
+ for _ in data {
+ diagnostics::span_lint(ctx, EMPTY_QUERY, *span, "Empty Query");
+ }
+ }
+ }
+
+ if !contradiction {
+ for option in &self.option {
+ QueryData::check_for_unnecessary_option(ctx, option, &[self]);
+ }
+ }
+ }
+ }
+
+ fn check_for_unnecessary_option(
+ ctx: &LateContext,
+ option: &(QueryData<'tcx>, Span),
+ facts: &[&QueryData<'tcx>],
+ ) {
+ if option.0.count() >= 2 {
+ option.0.lint_query_data(ctx, &option.1, facts);
+ }
+
+ if !option.0.option.is_empty() {
+ let mut new_facts = Vec::from(facts);
+ new_facts.push(&option.0);
+
+ for sub_option in &option.0.option {
+ QueryData::check_for_unnecessary_option(ctx, sub_option, &new_facts);
+ }
+ }
+
+ let mut vec_with = Vec::new();
+ let mut vec_without = Vec::new();
+ let mut vec_change = Vec::new();
+
+ for fact in facts {
+ vec_with.extend(keys(&fact.data).chain(keys(&fact.with)));
+ vec_without.extend(keys(&fact.without));
+ vec_change.extend(keys(&fact.added).chain(keys(&fact.changed)));
+ }
+
+ let option_fact: Vec<_> = keys(&option.0.data).chain(keys(&option.0.with)).collect();
+
+ let option_change: Vec<_> = keys(&option.0.added)
+ .chain(keys(&option.0.changed))
+ .collect();
+
+ if (!option_fact
+ .iter()
+ .any(|ty_kind| !vec_with.contains(ty_kind) && !vec_change.contains(ty_kind))
+ && !option_change
+ .iter()
+ .any(|ty_kind| !vec_change.contains(ty_kind))
+ && !keys(&option.0.without).any(|ty_kind| vec_with.contains(&ty_kind)))
+ || (option.0.count() - option.0.option.len() == 0)
+ {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_OPTION,
+ option.1,
+ "`Option` Query is always `Some`",
+ );
+ } else if option_fact.iter().any(|ty_kind| {
+ vec_without.contains(ty_kind) || keys(&option.0.without).contains(ty_kind)
+ }) || keys(&option.0.without).any(|ty_kind| vec_with.contains(&ty_kind))
+ {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_OPTION,
+ option.1,
+ "`Option` Query is always `None`",
+ );
+ }
+ }
+
+ fn check_for_unnecessary_or(&self, ctx: &LateContext, facts: &[&QueryData<'tcx>]) {
+ // TODO: Also handle nested ANDs `Or<(With<A>, Without<A>), With<B>)>`
+ for concrete_or in &self.or {
+ if concrete_or.0.len() < 2 {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_OPTION,
+ concrete_or.1,
+ "Unnecessary `Or` Filter",
+ );
+ return;
+ }
+
+ let mut vec_with = Vec::new();
+ let mut vec_without = Vec::new();
+ let mut vec_change = Vec::new();
+
+ for fact in facts {
+ vec_with.extend(keys(&fact.data).chain(keys(&fact.with)));
+ vec_without.extend(keys(&fact.without));
+ vec_change.extend(keys(&fact.added).chain(keys(&fact.changed)));
+ }
+
+ for part in &concrete_or.0 {
+ if !keys(&part.with)
+ .any(|ty_kind| !vec_with.contains(&ty_kind) && !vec_change.contains(&ty_kind))
+ && !keys(&part.added)
+ .chain(keys(&part.changed))
+ .any(|ty_kind| !vec_change.contains(&ty_kind))
+ && !keys(&part.without).any(|ty_kind| !vec_without.contains(&ty_kind))
+ {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_OPTION,
+ concrete_or.1,
+ "Unnecessary `Or` Filter",
+ );
+ break;
+ }
+ }
+
+ for (added_index, added) in concrete_or
+ .0
+ .iter()
+ .enumerate()
+ .filter(|(_, data)| !data.added.is_empty())
+ .map(|(index, data)| (index, &data.added))
+ {
+ for changed in concrete_or
+ .0
+ .iter()
+ .enumerate()
+ .filter(|(index, data)| *index != added_index && !data.changed.is_empty())
+ .map(|(_, data)| &data.changed)
+ {
+ for (ty_kind, spans) in added {
+ if contains_ty_kind(changed, ty_kind) {
+ for span in spans {
+ diagnostics::span_lint(
+ ctx,
+ UNNECESSARY_ADDED,
+ *span,
+ "Unnecessary `Added` Filter",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn count(&self) -> usize {
+ self.data.len()
+ + self.option.len()
+ + self.with.len()
+ + self.without.len()
+ + self.added.len()
+ + self.changed.len()
+ + self.or.len()
+ }
+}
+
+fn contains_ty_kind<'tcx, T>(vec: &Vec<(TyKind<'tcx>, T)>, ty_kind: &TyKind<'tcx>) -> bool {
+ for (kind, _) in vec {
+ if kind == ty_kind {
+ return true;
+ }
+ }
+ false
+}
+
+fn keys<'t, 'tcx, T>(vec: &'t [(TyKind<'tcx>, T)]) -> impl Iterator<Item = TyKind<'tcx>> + 't {
+ vec.iter().map(|(ty_kind, _)| *ty_kind)
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum QueryDataMeta {
+ Default,
+ Option,
+}
+
+impl Default for QueryDataMeta {
+ fn default() -> Self {
+ Self::Default
+ }
+}
+
+pub(super) fn lint_query(ctx: &LateContext, query: Query) {
+ let query_data = {
+ let mut query_data = QueryData::default();
+ query_data.fill_with_world_query(&query.world_query);
+ query_data.fill_with_filter_query(&query.filter_query);
+ query_data
+ };
+
+ query_data.lint_query_data(ctx, &query.span, &[]);
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`