Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

needless_borrows_for_generic_args: Fix for &mut #12892

Merged
merged 2 commits into from
Jul 25, 2024
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
48 changes: 43 additions & 5 deletions clippy_lints/src/needless_borrows_for_generic_args.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::mir::PossibleBorrowerMap;
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{expr_use_ctxt, peel_n_hir_expr_refs, DefinedTy, ExprUseNode};
Expand All @@ -11,6 +11,7 @@ use rustc_hir::{Body, Expr, ExprKind, Mutability, Path, QPath};
use rustc_index::bit_set::BitSet;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::{Rvalue, StatementKind};
use rustc_middle::ty::{
self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty,
};
Expand Down Expand Up @@ -105,6 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
}
&& let count = needless_borrow_count(
cx,
&mut self.possible_borrowers,
fn_id,
cx.typeck_results().node_args(hir_id),
i,
Expand Down Expand Up @@ -153,9 +155,14 @@ fn path_has_args(p: &QPath<'_>) -> bool {
/// The following constraints will be checked:
/// * The borrowed expression meets all the generic type's constraints.
/// * The generic type appears only once in the functions signature.
/// * The borrowed value is Copy itself OR not a variable (created by a function call)
/// * The borrowed value is:
/// - `Copy` itself, or
/// - the only use of a mutable reference, or
/// - not a variable (created by a function call)
#[expect(clippy::too_many_arguments)]
fn needless_borrow_count<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
fn_id: DefId,
callee_args: ty::GenericArgsRef<'tcx>,
arg_index: usize,
Expand Down Expand Up @@ -230,9 +237,9 @@ fn needless_borrow_count<'tcx>(

let referent_ty = cx.typeck_results().expr_ty(referent);

if (!is_copy(cx, referent_ty) && !referent_ty.is_ref())
&& let ExprKind::AddrOf(_, _, inner) = reference.kind
&& !matches!(inner.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
if !(is_copy(cx, referent_ty)
|| referent_ty.is_ref() && referent_used_exactly_once(cx, possible_borrowers, reference)
Copy link
Member

Choose a reason for hiding this comment

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

In the PR description you mention:

To avoid emitting false suggestions, this PR reintroduces the referent_used_exactly_once logic and runs that check for referents that are themselves mutable references.

Where is the mutability checked?


Alos, when is this check ever run? &T/&mut T should both be copy, which should short circit the || on this line. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

&mut T is not copy, as it must be unique. Observe how #[derive(Clone, Copy)] struct Meow<'a>(&'a mut i32); doesn't compile.

In most contexts it does behave like it is Copy, but under the hood it's actually a reborrow – and when passing a &mut to a function that takes impl Trait, the coercion that causes the reborrow doesn't happen.

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, interesting TIL. Then this checks out

|| matches!(referent.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)))
{
return false;
}
Expand Down Expand Up @@ -335,6 +342,37 @@ fn is_mixed_projection_predicate<'tcx>(
}
}

fn referent_used_exactly_once<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
reference: &Expr<'tcx>,
) -> bool {
if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id)
&& let Some(local) = expr_local(cx.tcx, reference)
&& let [location] = *local_assignments(mir, local).as_slice()
&& let block_data = &mir.basic_blocks[location.block]
&& let Some(statement) = block_data.statements.get(location.statement_index)
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
&& !place.is_indirect_first_projection()
{
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
if possible_borrowers
.last()
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
{
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
}
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
// itself. See the comment in that method for an explanation as to why.
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
&& used_exactly_once(mir, place.local).unwrap_or(false)
} else {
false
}
}

// Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting
// projected type that is a type parameter. Returns `false` if replacing the types would have an
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/needless_borrows_for_generic_args.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,12 @@ fn main() {
f(&y); // Don't lint
f("".to_owned()); // Lint
}
{
fn takes_writer<T: std::io::Write>(_: T) {}

fn issue_12856(mut buffer: &mut Vec<u8>) {
takes_writer(&mut buffer); // Don't lint, would make buffer unavailable later
buffer.extend(b"\n");
}
}
}
8 changes: 8 additions & 0 deletions tests/ui/needless_borrows_for_generic_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,12 @@ fn main() {
f(&y); // Don't lint
f(&"".to_owned()); // Lint
}
{
fn takes_writer<T: std::io::Write>(_: T) {}

fn issue_12856(mut buffer: &mut Vec<u8>) {
takes_writer(&mut buffer); // Don't lint, would make buffer unavailable later
buffer.extend(b"\n");
}
}
}