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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

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

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

# Easy error propagation and contexts.
anyhow = "1.0.86"
Expand Down
2 changes: 1 addition & 1 deletion bevy_lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ There are several other ways to toggle lints, although some have varying levels

|`bevy_lint` Version|Rust Version|Rustup Toolchain|Bevy Version|
|-|-|-|-|
|0.3.0-dev|1.87.0|`nightly-2025-02-20`|0.16|
|0.3.0-dev|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|

Expand Down
49 changes: 31 additions & 18 deletions bevy_lint/src/lints/pedantic/borrowed_reborrowable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,16 @@
use std::ops::ControlFlow;

use crate::{declare_bevy_lint, declare_bevy_lint_pass};
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_opt, ty::match_type};
use clippy_utils::{
diagnostics::span_lint_and_sugg,
source::{snippet, snippet_opt},
ty::match_type,
};
use rustc_errors::Applicability;
use rustc_hir::{Body, FnDecl, MutTy, Mutability, intravisit::FnKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{Interner, Ty, TyKind, TypeVisitable, TypeVisitor};
use rustc_span::{
Span,
def_id::LocalDefId,
symbol::{Ident, kw},
};
use rustc_span::{Span, def_id::LocalDefId, symbol::kw};

declare_bevy_lint! {
pub BORROWED_REBORROWABLE,
Expand All @@ -126,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable {
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'tcx>,
_: &'tcx Body<'tcx>,
body: &'tcx Body<'tcx>,
fn_span: Span,
def_id: LocalDefId,
) {
Expand All @@ -142,26 +142,38 @@ impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable {
_ => cx.tcx.fn_sig(def_id).instantiate_identity(),
};

// 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;

let args = fn_sig.inputs().skip_binder();
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(),
"there must be the same number of argument types and parameters"
);

for (arg_index, arg_ty) in args.iter().enumerate() {
for (arg_index, arg_ty) in arg_types.iter().enumerate() {
let TyKind::Ref(region, ty, Mutability::Mut) = arg_ty.kind() else {
// We only care about `&mut` parameters
continue;
};

let arg_ident = arg_names[arg_index];

// 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_ident.name == kw::SelfLower {
if arg_names[arg_index].is_some_and(|ident| ident.name == kw::SelfLower) {
continue;
}

Expand All @@ -187,8 +199,6 @@ impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable {
continue;
}

let span = decl.inputs[arg_index].span.to(arg_ident.span);

// This tries to get the user-written form of `T` given the HIR representation for `&T`
// / `&mut T`. If we cannot for whatever reason, we fallback to using
// `Ty::to_string()` to get the fully-qualified form of `T`.
Expand All @@ -205,15 +215,17 @@ impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable {
// If it's not a `Ref` for whatever reason, fallback to our default value.
_ => None,
}
// We previously peeled the `&mut` reference, so `ty` is just the underlying `T`.
.unwrap_or_else(|| ty.to_string());

span_lint_and_sugg(
cx,
BORROWED_REBORROWABLE.lint,
span,
// The span contains both the argument name and type.
arg_params[arg_index].span,
reborrowable.message(),
reborrowable.help(),
reborrowable.suggest(arg_ident, ty_snippet),
reborrowable.suggest(cx, arg_params[arg_index].pat.span, ty_snippet),
// Not machine-applicable since the function body may need to
// also be updated to account for the removed ref
Applicability::MaybeIncorrect,
Expand Down Expand Up @@ -293,8 +305,9 @@ impl Reborrowable {
format!("use `{name}` instead")
}

fn suggest(&self, ident: Ident, ty: String) -> String {
format!("mut {ident}: {ty}")
fn suggest(&self, cx: &LateContext, name: Span, ty: String) -> String {
let name = snippet(cx, name, "_");
format!("mut {name}: {ty}")
}
}

Expand Down
12 changes: 8 additions & 4 deletions bevy_lint/src/utils/hir_parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_hir::{
def::{DefKind, Res},
};
use rustc_lint::LateContext;
use rustc_span::{Span, Symbol};
use rustc_span::{Ident, Span, kw};

/// Returns the list of types inside a tuple type.
///
Expand Down Expand Up @@ -243,9 +243,13 @@ impl<'tcx> MethodCall<'tcx> {
// 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
// argument name.
if inputs
.first()
.is_some_and(|ident| ident.name == Symbol::intern("self"))
if let [
Some(Ident {
name: kw::SelfLower,
..
}),
..,
] = inputs
{
let method_path = match *qpath {
// If the qualified path is resolved, the method path must be the final
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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-02-20"
channel = "nightly-2025-04-03"
# These components are required to use `rustc` crates.
components = ["rustc-dev", "llvm-tools-preview"]
13 changes: 5 additions & 8 deletions src/external_cli/cargo/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,26 +90,23 @@ pub struct Package {
impl Package {
/// Check if the package has an executable binary.
pub fn has_bin(&self) -> bool {
self.targets.iter().any(|target| {
target
.kind
.iter()
.any(|target_kind| *target_kind == TargetKind::Bin)
})
self.targets
.iter()
.any(|target| target.kind.contains(&TargetKind::Bin))
}

/// An iterator over all binary targets contained in this package.
pub fn bin_targets(&self) -> impl Iterator<Item = &Target> {
self.targets
.iter()
.filter(|target| target.kind.iter().any(|kind| *kind == TargetKind::Bin))
.filter(|target| target.kind.contains(&TargetKind::Bin))
}

/// An iterator over all example targets contained in this package.
pub fn example_targets(&self) -> impl Iterator<Item = &Target> {
self.targets
.iter()
.filter(|target| target.kind.iter().any(|kind| *kind == TargetKind::Example))
.filter(|target| target.kind.contains(&TargetKind::Example))
}
}

Expand Down