Skip to content
Open
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
73 changes: 50 additions & 23 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,25 +505,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
}
};

if coerced_a == a && mt_a.mutbl.is_not() && autoderef.step_count() == 1 {
// As a special case, if we would produce `&'a *x`, that's
// a total no-op. We end up with the type `&'a T` just as
// we started with. In that case, just skip it altogether.
//
// Unfortunately, this can actually effect capture analysis
// which in turn means this effects borrow checking. This can
// also effect diagnostics.
// FIXME(BoxyUwU): we should always emit reborrow coercions
//
// Note that for `&mut`, we DO want to reborrow --
// otherwise, this would be a move, which might be an
// error. For example `foo(self.x)` where `self` and
// `self.x` both have `&mut `type would be a move of
// `self.x`, but we auto-coerce it to `foo(&mut *self.x)`,
// which is a borrow.
assert!(mutbl_b.is_not()); // can only coerce &T -> &U
return success(vec![], coerced_a, obligations);
}
let autoderef_step_count = autoderef.step_count();

let InferOk { value: mut adjustments, obligations: o } =
self.adjust_steps_as_infer_ok(&autoderef);
Expand All @@ -536,10 +518,45 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
coerced_a
);

// Now apply the autoref
let mutbl = AutoBorrowMutability::new(mutbl_b, self.allow_two_phase);
adjustments
.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), target: coerced_a });
// As a special case, if we would produce `&'a *x`, that's
// a total no-op. We end up with the type `&'a T` just as
// we started with. In that case, just skip it altogether.
//
// FIXME: Ideally we would also skip `&mut` -> `&mut` reborrow,
// and handle that in MIR.
let can_skip_noop_reborrow = mutbl_b.is_not()
&& if self.tcx.is_closure_like(self.body_def_id.to_def_id()) {
// FIXME: Inside a closure, reborrows can affect capture inference.
// So we have to keep the existing elision behavior stable.
// Ideally we wouldn't have to do this, it makes lifetime
// annotations affect codegen
coerced_a == a && autoderef_step_count == 1
} else {
if let Some(Adjustment { kind: Adjust::Deref(DerefAdjustKind::Builtin), .. }) =
adjustments.last()
// Check type before the last deref adjustment
&& let ty::Ref(_, _, ty::Mutability::Not) = if adjustments.len() >= 2 {
*adjustments[adjustments.len() - 2].target.kind()
} else {
ty::Ref(r_a, mt_a.ty, mt_a.mutbl)
}
{
true
} else {
false
}
};

if can_skip_noop_reborrow {
adjustments.pop();
} else {
// Now apply the autoref
let mutbl = AutoBorrowMutability::new(mutbl_b, self.allow_two_phase);
adjustments.push(Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)),
target: coerced_a,
});
}

debug!("coerce_to_ref: succeeded coerced_a={:?} adjustments={:?}", coerced_a, adjustments);

Expand Down Expand Up @@ -630,6 +647,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {

// Handle reborrows before selecting `Source: CoerceUnsized<Target>`.
let reborrow = match (source.kind(), target.kind()) {
// Don't emit a no-op shared -> shared reborrow.
(&ty::Ref(_, _, ty::Mutability::Not), &ty::Ref(_, _, ty::Mutability::Not))
// FIXME: Inside a closure, reborrows can affect capture inference.
// So we have to emit them anyway.
// Ideally we wouldn't have to do this, it makes lifetime
// annotations affect codegen
if !self.tcx.is_closure_like(self.body_def_id.to_def_id()) =>
{
None
}
(&ty::Ref(_, ty_a, mutbl_a), &ty::Ref(_, _, mutbl_b)) => {
coerce_mutbls(mutbl_a, mutbl_b)?;

Expand Down
22 changes: 18 additions & 4 deletions compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,24 @@ impl<'tcx> ThirBuildCx<'tcx> {
deref.span,
)
}
Adjust::Borrow(AutoBorrow::Ref(m)) => ExprKind::Borrow {
borrow_kind: m.to_borrow_kind(),
arg: self.thir.exprs.push(expr),
},
Adjust::Borrow(AutoBorrow::Ref(m)) => {
// Don't emit no-op &T -> &T reborrows
if let AutoBorrowMutability::Not = m
&& let ExprKind::Deref { arg: derefed_expr } = expr.kind
&& let ty::Ref(_, _, ty::Mutability::Not) =
self.thir.exprs[derefed_expr].ty.kind()
// FIXME: reborrows can currently affect closure capture inference
&& !self.tcx.is_closure_like(self.body_owner)
{
debug_assert_eq!(self.thir.exprs.last_index(), Some(derefed_expr));
return self.thir.exprs.pop().unwrap();
} else {
ExprKind::Borrow {
borrow_kind: m.to_borrow_kind(),
arg: self.thir.exprs.push(expr),
}
}
}
Adjust::Borrow(AutoBorrow::RawPtr(mutability)) => {
ExprKind::RawBorrow { mutability, arg: self.thir.exprs.push(expr) }
}
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/vec/spec_extend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl<'a, T: 'a, A: Allocator> SpecExtend<&'a T, slice::Iter<'a, T>> for Vec<T, A
where
T: TrivialClone,
{
#[inline]

@Jules-Bertholet Jules-Bertholet Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To make tests/codegen-llvm/lib-optimizations/append-elements.rs happy. That test is really fragile…

View changes since the review

fn spec_extend(&mut self, iterator: slice::Iter<'a, T>) {
let slice = iterator.as_slice();
// ignore-tidy-undocumented-unsafe
Expand Down
21 changes: 13 additions & 8 deletions src/tools/clippy/clippy_lints/src/dereference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use rustc_hir::{
Node, OwnerId, Pat, PatKind, Path, QPath, TyKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass, impl_lint_pass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, DerefAdjustKind};
use rustc_middle::ty::{self, AssocTag, Ty, TyCtxt, TypeVisitableExt as _, TypeckResults, Unnormalized};
use rustc_span::{Span, Symbol, SyntaxContext};
use std::borrow::Cow;
Expand Down Expand Up @@ -350,6 +350,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
None => break None,
}
};
if use_site.adjustments.last().is_some_and(|adj| {
matches!(adj.kind, Adjust::Deref(DerefAdjustKind::Builtin))
&& matches!(adj.target.kind(), ty::Ref(_, _, Mutability::Not))
}) {
deref_count += 1;
}

let use_node = use_site.use_node(cx);
let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| {
Expand Down Expand Up @@ -1041,14 +1047,13 @@ impl<'tcx> Dereferencing<'tcx> {
[
Adjustment {
kind: Adjust::Deref(_),
..
},
Adjustment {
kind: Adjust::Deref(_),
..
target
},
..
]
rest @ ..
] if match rest.first() {
Some(adj) => matches!(adj.kind,Adjust::Deref(_)),
None => matches!(target.kind(), ty::Ref(_, _, Mutability::Not)),
}
)
{
match get_parent_expr(cx, e) {
Expand Down
24 changes: 18 additions & 6 deletions src/tools/clippy/clippy_lints/src/methods/clone_on_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use rustc_attr_ir::lang_items::LangItem;
use rustc_errors::Applicability;
use rustc_hir::{BindingMode, ByRef, Expr, ExprKind, MatchSource, Node, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_middle::ty::{self};

use super::CLONE_ON_COPY;

Expand Down Expand Up @@ -141,11 +141,23 @@ pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>) {
let snip = snippet_with_context(cx, peeled_arg.span, func.span.ctxt(), "_", &mut app).0;

let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
let deref_count = arg_adjustments
.iter()

let adjustments_iter = &mut arg_adjustments.iter();
let mut deref_count = adjustments_iter
.take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
.count()
- ref_count;
.count();

if !matches!(
adjustments_iter.next(),
Some(Adjustment {
kind: Adjust::Borrow(_),
..
})
) {
deref_count += 1;
}

deref_count -= ref_count;

let (help, sugg) = if deref_count == 0 {
if check_res == ParentIsSuffixExpr::Yes && snip.starts_with('*') {
Expand Down
26 changes: 20 additions & 6 deletions src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,20 @@ fn check_addr_of_expr(
) -> bool {
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind
&& let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>()
&& let
&& let adjustments = cx.typeck_results().expr_adjustments(parent)
&& let (referent_ty, target_ty) = match *adjustments {
[] if let target_ty = cx.typeck_results().expr_ty_adjusted(parent) &&
let &ty::Ref(_, referent_ty, Mutability::Not) = target_ty.kind() => {
(referent_ty, target_ty)
}
[
Adjustment {
kind: Adjust::Pointer(_),
target: target_ty,
},
] if let &ty::Ref(_, referent_ty, Mutability::Not) = cx.typeck_results().expr_ty(parent).kind() => {
(referent_ty, target_ty)
}
// For matching uses of `Cow::from`
[
Adjustment {
Expand Down Expand Up @@ -116,9 +128,11 @@ fn check_addr_of_expr(
kind: Adjust::Borrow(_),
target: target_ty,
},
] = adjustments[..]
] => (referent_ty, target_ty),
_ => return false,
}
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let (target_ty, n_target_refs, _) = peel_and_count_ty_refs(*target_ty)
&& let (target_ty, n_target_refs, _) = peel_and_count_ty_refs(target_ty)
&& let (receiver_ty, n_receiver_refs, _) = peel_and_count_ty_refs(receiver_ty)
// Only flag cases satisfying at least one of the following three conditions:
// * the referent and receiver types are distinct
Expand All @@ -129,8 +143,8 @@ fn check_addr_of_expr(
// https://github.com/rust-lang/rust-clippy/issues/8759
// Arrays are a bit of a corner case. Non-copyable arrays are handled by
// `redundant_clone`, but copyable arrays are not.
&& (*referent_ty != receiver_ty
|| (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
&& (referent_ty != receiver_ty
|| (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, referent_ty))
|| is_cow_into_owned(cx, method_name, method_parent_id))
{
let mut applicability = Applicability::MachineApplicable;
Expand Down
13 changes: 6 additions & 7 deletions src/tools/clippy/clippy_lints/src/redundant_clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,18 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
continue;
}

// `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }`
let Some((cloned, cannot_move_out)) = find_stmt_assigns_to(cx, mir, arg, from_borrow, bb) else {
continue;
};

let loc = mir::Location {
block: bb,
statement_index: bbdata.statements.len(),
};

// `Local` to be cloned, and a local of `clone` call's destination
let (local, ret_local) = if from_borrow {
// `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }`
let Some((cloned, cannot_move_out)) = find_stmt_assigns_to(cx, mir, arg, from_borrow, bb) else {
continue;
};

// `res = clone(arg)` can be turned into `res = move arg;`
// if `arg` is the only borrow of `cloned` at this point.

Expand All @@ -149,7 +149,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
// receiver of the `deref()` call
let (pred_arg, deref_clone_ret) = if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) =
is_call_with_ref_arg(cx, mir, &pred_terminator.kind)
&& res == cloned
&& cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id)
&& let ty::Adt(pred_arg_def, _) = pred_arg_ty.kind()
&& let Some(pred_arg_name) = cx.tcx.get_diagnostic_name(pred_arg_def.did())
Expand Down Expand Up @@ -178,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
// StorageDead(pred_arg);
// res = to_path_buf(cloned);
// ```
if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) {
if cannot_move_out || !possible_borrower.only_borrowers(&[arg, deref_clone_ret], local, loc) {
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/tests/ui/let_and_return.edition2021.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ mod issue_11335 {
pub fn inner(&self) -> &T {


(match self {
match self {
E::A(x) => x,
E::B(x) => x,
}) as _
}
//~^ let_and_return
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/tests/ui/let_and_return.edition2021.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ help: return the expression directly
|
LL ~
LL |
LL ~ (match self {
LL ~ match self {
LL + E::A(x) => x,
LL + E::B(x) => x,
LL + }) as _
LL + }
|

error: returning the result of a `let` binding from a block
Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/tests/ui/let_and_return.edition2024.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ mod issue_11335 {
pub fn inner(&self) -> &T {


(match self {
match self {
E::A(x) => x,
E::B(x) => x,
}) as _
}
//~^ let_and_return
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/tests/ui/let_and_return.edition2024.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ help: return the expression directly
|
LL ~
LL |
LL ~ (match self {
LL ~ match self {
LL + E::A(x) => x,
LL + E::B(x) => x,
LL + }) as _
LL + }
|

error: returning the result of a `let` binding from a block
Expand Down
2 changes: 1 addition & 1 deletion tests/codegen-llvm/lib-optimizations/append-elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#[no_mangle]
pub fn vec_append_with_temp_alloc(dst: &mut Vec<u8>, src: &[u8]) {
// CHECK-NOT: call void @llvm.memcpy
// CHECK: call void @llvm.memcpy.{{.*}}[[DST]].i{{.*}}[[SRC]]
// CHECK: call void @llvm.memcpy.{{.*}}{{.*}}.i{{.*}}[[SRC]]
// CHECK-NOT: call void @llvm.memcpy
let temp = src.to_vec();
dst.extend(&temp);
Expand Down
8 changes: 4 additions & 4 deletions tests/incremental/hashes/call_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,16 @@ pub fn change_argument_method_ufcs() {
#[cfg(any(bpass1,bpass4))]
pub fn change_to_ufcs() {
let s = Struct;
s.method1('x', true); // ------
s.method1('x', true);
}

#[cfg(not(any(bpass1,bpass4)))]
#[rustc_clean(cfg="bpass2", except="hir_owner,optimized_mir,typeck_root")]
#[rustc_clean(cfg="bpass2", except="hir_owner,typeck_root")]
#[rustc_clean(cfg="bpass3")]
// MIR changes because spans change, and there is no `-Zincremental-ignore-spans`
// for the last 3 revisions
#[rustc_clean(cfg="bpass5", except="hir_owner,optimized_mir,typeck_root")]
#[rustc_clean(cfg="bpass6")]
// One might think this would be expanded in the hir_owner/Mir, but it actually
// results in slightly different hir_owner/Mir.
pub fn change_to_ufcs() {
let s = Struct;
Struct::method1(&s, 'x', true);
Expand Down
Loading
Loading