From b15544d52fc005ca0142fb34a40d591147596c8c Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 23 Apr 2026 11:17:22 +0100 Subject: [PATCH 1/8] Handle index projections in call destinations in DSE Since call destinations are evaluated after call arguments, we can't turn copy arguments into moves if the same local is later used as an index projection in the call destination. --- .../src/dead_store_elimination.rs | 10 +++++++- ...eadStoreElimination-final.panic-abort.diff | 15 ++++++++++++ ...adStoreElimination-final.panic-unwind.diff | 15 ++++++++++++ .../dead-store-elimination/call_arg_copy.rs | 24 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff create mode 100644 tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs index 63ee69322eef0..e968ed640ecf1 100644 --- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs +++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs @@ -47,13 +47,21 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { let mut patch = Vec::new(); for (bb, bb_data) in traversal::preorder(body) { - if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind { + if let TerminatorKind::Call { ref args, ref destination, .. } = bb_data.terminator().kind { let loc = Location { block: bb, statement_index: bb_data.statements.len() }; // Position ourselves between the evaluation of `args` and the write to `destination`. live.seek_to_block_end(bb); let mut state = live.get().clone(); + // Don't turn into a move if the local is used as an index + // projection for the destination place. + LivenessTransferFunction(&mut state).visit_place( + destination, + visit::PlaceContext::MutatingUse(visit::MutatingUseContext::Call), + loc, + ); + for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() { if let Operand::Copy(place) = *arg && !place.is_indirect() diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff new file mode 100644 index 0000000000000..012dd7d88a5f0 --- /dev/null +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff @@ -0,0 +1,15 @@ +- // MIR for `move_index` before DeadStoreElimination-final ++ // MIR for `move_index` after DeadStoreElimination-final + + fn move_index(_1: [usize; 10], _2: usize) -> () { + let mut _0: (); + + bb0: { + _1[_2] = passthrough_usize(copy _2) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } + } + diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff new file mode 100644 index 0000000000000..fcd0ae43e2897 --- /dev/null +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff @@ -0,0 +1,15 @@ +- // MIR for `move_index` before DeadStoreElimination-final ++ // MIR for `move_index` after DeadStoreElimination-final + + fn move_index(_1: [usize; 10], _2: usize) -> () { + let mut _0: (); + + bb0: { + _1[_2] = passthrough_usize(copy _2) -> [return: bb1, unwind continue]; + } + + bb1: { + return; + } + } + diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.rs b/tests/mir-opt/dead-store-elimination/call_arg_copy.rs index 27b5ccdb936db..00a9a49c2abb8 100644 --- a/tests/mir-opt/dead-store-elimination/call_arg_copy.rs +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.rs @@ -40,7 +40,31 @@ fn move_packed(packed: Packed) { } } +#[inline(never)] +fn passthrough_usize(a: usize) -> usize { + a +} + +// EMIT_MIR call_arg_copy.move_index.DeadStoreElimination-final.diff +#[custom_mir(dialect = "analysis")] +fn move_index(a: [usize; 10], b: usize) { + // CHECK-LABEL: fn move_index( + // CHECK: = passthrough_usize(copy _2) + mir! { + { + // The index is used again after the operand is evaluated to + // evaluate the destionation place, so the argument cannot be turned + // into a move. + Call(a[b] = passthrough_usize(b), ReturnTo(ret), UnwindContinue()) + } + ret = { + Return() + } + } +} + fn main() { move_simple(1); move_packed(Packed { x: 0, y: 1 }); + move_index([0; _], 1); } From bbdc7c4cc5f80369ad94d4ac1fc5cf7da1f93ba0 Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Fri, 24 Apr 2026 13:59:31 +0200 Subject: [PATCH 2/8] bootstrap: Don't clone submodules unconditionally in dry-run This made it very annoying to debug bootstrap itself, because every `--dry-run` invocation would start out by cloning LLVM, which is almost never necessary. Instead change a few Steps to properly support dry_run when no submodule is checked out. --- src/bootstrap/src/core/config/config.rs | 21 +++++---------------- src/bootstrap/src/lib.rs | 4 ++++ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index babcafb024e07..b933e668c26ae 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -2448,17 +2448,7 @@ pub(crate) fn update_submodule<'a>( return; } - // Submodule updating actually happens during in the dry run mode. We need to make sure that - // all the git commands below are actually executed, because some follow-up code - // in bootstrap might depend on the submodules being checked out. Furthermore, not all - // the command executions below work with an empty output (produced during dry run). - // Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in - // dry run mode. - let submodule_git = || { - let mut cmd = helpers::git(Some(&absolute_path)); - cmd.run_in_dry_run(); - cmd - }; + let submodule_git = || helpers::git(Some(&absolute_path)); // Determine commit checked out in submodule. let checked_out_hash = @@ -2466,7 +2456,7 @@ pub(crate) fn update_submodule<'a>( let checked_out_hash = checked_out_hash.trim_end(); // Determine commit that the submodule *should* have. let recorded = helpers::git(Some(dwn_ctx.src)) - .run_in_dry_run() + .run_in_dry_run() // otherwise parsing `actual_hash` fails .args(["ls-tree", "HEAD"]) .arg(relative_path) .run_capture_stdout(dwn_ctx.exec_ctx) @@ -2482,11 +2472,12 @@ pub(crate) fn update_submodule<'a>( return; } - println!("Updating submodule {relative_path}"); + if !dwn_ctx.exec_ctx.dry_run() { + println!("Updating submodule {relative_path}"); + }; helpers::git(Some(dwn_ctx.src)) .allow_failure() - .run_in_dry_run() .args(["submodule", "-q", "sync"]) .arg(relative_path) .run(dwn_ctx.exec_ctx); @@ -2497,12 +2488,10 @@ pub(crate) fn update_submodule<'a>( // even though that has no relation to the upstream for the submodule. let current_branch = helpers::git(Some(dwn_ctx.src)) .allow_failure() - .run_in_dry_run() .args(["symbolic-ref", "--short", "HEAD"]) .run_capture(dwn_ctx.exec_ctx); let mut git = helpers::git(Some(dwn_ctx.src)).allow_failure(); - git.run_in_dry_run(); if current_branch.is_success() { // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name. // This syntax isn't accepted by `branch.{branch}`. Strip it. diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 6af11d9ae0a81..fd3d129c231d7 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -676,6 +676,10 @@ impl Build { return; } + if self.config.dry_run() { + return; + } + // When testing bootstrap itself, it is much faster to ignore // submodules. Almost all Steps work fine without their submodules. if cfg!(test) && !self.config.submodules() { From 84ebb2269e4b530789b6f7bae00b1ec9a80bc630 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:54:21 -0500 Subject: [PATCH 3/8] account for `GetSyntheticValue` failures --- src/etc/lldb_providers.py | 53 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index ef3f94ee83530..df2bdf8e58a3d 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -108,8 +108,6 @@ def num_children(self) -> int: return self.valobj.GetNumChildren() def get_child_index(self, name: str) -> int: - if self.is_ptr and name == "$$dereference$$": - return self.valobj.Dereference().GetSyntheticValue() return self.valobj.GetIndexOfChildWithName(name) def get_child_at_index(self, index: int) -> Optional[SBValue]: @@ -133,13 +131,17 @@ def num_children(self) -> int: return 1 def get_child_index(self, name: str) -> int: - if self.is_ptr and name == "$$dereference$$": + if name == "$$dereference$$": return 0 return -1 def get_child_at_index(self, index: int) -> Optional[SBValue]: if index == 0: - return self.valobj.Dereference().GetSyntheticValue() + value = self.valobj.Dereference() + if (synth := value.GetSyntheticValue()).IsValid(): + return synth + else: + return value return None def update(self): @@ -569,7 +571,9 @@ def update(self): self.variant = all_variants.GetChildAtIndex(index) self.value = self.variant.GetChildMemberWithName( ClangEncodedEnumProvider.VALUE_MEMBER_NAME - ).GetSyntheticValue() + ) + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth def _getCurrentVariantIndex(self, all_variants: SBValue) -> int: default_index = 0 @@ -651,9 +655,10 @@ def update(self): ).GetValueAsUnsigned() if tag == discr: self.variant = child - self.value = child.GetChildMemberWithName( - "value" - ).GetSyntheticValue() + self.value = child.GetChildMemberWithName("value") + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth + return else: # if invalid, DISCR must be a range begin: int = ( @@ -671,16 +676,18 @@ def update(self): if begin < end: if begin <= tag <= end: self.variant = child - self.value = child.GetChildMemberWithName( - "value" - ).GetSyntheticValue() + self.value = child.GetChildMemberWithName("value") + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth + return else: if tag >= begin or tag <= end: self.variant = child - self.value = child.GetChildMemberWithName( - "value" - ).GetSyntheticValue() + self.value = child.GetChildMemberWithName("value") + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth + return else: # if invalid, tag is a 128 bit value tag_lo: int = self.valobj.GetChildMemberWithName( @@ -714,9 +721,9 @@ def update(self): discr: int = (exact_hi << 64) | exact_lo if tag == discr: self.variant = child - self.value = child.GetChildMemberWithName( - "value" - ).GetSyntheticValue() + self.value = child.GetChildMemberWithName("value") + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth return else: # if invalid, DISCR must be a range begin_lo: int = ( @@ -748,16 +755,16 @@ def update(self): if begin < end: if begin <= tag <= end: self.variant = child - self.value = child.GetChildMemberWithName( - "value" - ).GetSyntheticValue() + self.value = child.GetChildMemberWithName("value") + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth return else: if tag >= begin or tag <= end: self.variant = child - self.value = child.GetChildMemberWithName( - "value" - ).GetSyntheticValue() + self.value = child.GetChildMemberWithName("value") + if (synth := self.value.GetSyntheticValue()).IsValid(): + self.value = synth return def num_children(self) -> int: From f69946ac6497495315ff56052d6f41d5532216b9 Mon Sep 17 00:00:00 2001 From: yuk1ty Date: Sat, 21 Mar 2026 23:12:46 +0900 Subject: [PATCH 4/8] Avoid redundant clone suggestions in borrowck diagnostics --- .../src/diagnostics/conflict_errors.rs | 4 +- .../rustc_borrowck/src/diagnostics/mod.rs | 121 ++++++++++++------ .../src/diagnostics/move_errors.rs | 62 +++++---- ...ck-move-out-of-overloaded-auto-deref.fixed | 2 +- ...k-move-out-of-overloaded-auto-deref.stderr | 4 - .../borrowck/clone-span-on-try-operator.fixed | 2 +- .../clone-span-on-try-operator.stderr | 4 - ...move-upvar-from-non-once-ref-closure.fixed | 2 +- ...ove-upvar-from-non-once-ref-closure.stderr | 4 - tests/ui/moves/suggest-clone.fixed | 2 +- tests/ui/moves/suggest-clone.stderr | 4 - .../ui/suggestions/option-content-move.fixed | 4 +- .../ui/suggestions/option-content-move.stderr | 8 -- 13 files changed, 129 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 2735c800c4a38..639cb75fdd28b 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -254,7 +254,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { maybe_reinitialized_locations_is_empty: maybe_reinitialized_locations .is_empty(), }; - self.explain_captures( + // The return value indicates whether a clone suggestion was emitted; + // redundancy is already handled via the `FnSelfUse` check below. + let _ = self.explain_captures( &mut err, span, move_span, diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index a9697950e38be..3ee0369c9da6e 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -1002,6 +1002,12 @@ struct CapturedMessageOpt { maybe_reinitialized_locations_is_empty: bool, } +/// Tracks which suggestions were emitted by [`MirBorrowckCtxt::explain_captures`], +/// so callers can avoid emitting redundant suggestions downstream. +struct ExplainCapturesResult { + clone_suggestion: bool, +} + impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// Finds the spans associated to a move or copy of move_place at location. pub(super) fn move_spans( @@ -1226,7 +1232,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { move_spans: UseSpans<'tcx>, moved_place: Place<'tcx>, msg_opt: CapturedMessageOpt, - ) { + ) -> ExplainCapturesResult { let CapturedMessageOpt { is_partial_move: is_partial, is_loop_message, @@ -1235,6 +1241,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { has_suggest_reborrow, maybe_reinitialized_locations_is_empty, } = msg_opt; + let mut suggested_cloning = false; if let UseSpans::FnSelfUse { var_span, fn_call_span, fn_span, kind } = move_spans { let place_name = self .describe_place(moved_place.as_ref()) @@ -1461,10 +1468,26 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { has_sugg = true; } if let Some(clone_trait) = tcx.lang_items().clone_trait() { - let sugg = if moved_place + // Check whether the deref is from a custom Deref impl + // (e.g. Rc, Box) or a built-in reference deref. + // For built-in derefs with Clone fully satisfied, we skip + // the UFCS suggestion here and let `suggest_cloning` + // downstream emit a simpler `.clone()` suggestion instead. + let has_overloaded_deref = + moved_place.iter_projections().any(|(place, elem)| { + matches!(elem, ProjectionElem::Deref) + && matches!( + self.borrowed_content_source(place), + BorrowedContentSource::OverloadedDeref(_) + | BorrowedContentSource::OverloadedIndex(_) + ) + }); + + let has_deref = moved_place .iter_projections() - .any(|(_, elem)| matches!(elem, ProjectionElem::Deref)) - { + .any(|(_, elem)| matches!(elem, ProjectionElem::Deref)); + + let sugg = if has_deref { let (start, end) = if let Some(expr) = self.find_expr(move_span) && let Some(_) = self.clone_on_reference(expr) && let hir::ExprKind::MethodCall(_, rcvr, _, _) = expr.kind @@ -1490,43 +1513,58 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.infcx.param_env, ) && !has_sugg { - let msg = match &errors[..] { - [] => "you can `clone` the value and consume it, but this \ - might not be your desired behavior" - .to_string(), - [error] => { - format!( - "you could `clone` the value and consume it, if the \ - `{}` trait bound could be satisfied", - error.obligation.predicate, - ) - } - _ => { - format!( - "you could `clone` the value and consume it, if the \ - following trait bounds could be satisfied: {}", - listify(&errors, |e: &FulfillmentError<'tcx>| format!( - "`{}`", - e.obligation.predicate - )) - .unwrap(), - ) - } - }; - err.multipart_suggestion(msg, sugg, Applicability::MaybeIncorrect); - for error in errors { - if let FulfillmentErrorCode::Select( - SelectionError::Unimplemented, - ) = error.code - && let ty::PredicateKind::Clause(ty::ClauseKind::Trait( - pred, - )) = error.obligation.predicate.kind().skip_binder() - { - self.infcx.err_ctxt().suggest_derive( - &error.obligation, - err, - error.obligation.predicate.kind().rebind(pred), - ); + let skip_for_simple_clone = + has_deref && !has_overloaded_deref && errors.is_empty(); + if !skip_for_simple_clone { + let msg = match &errors[..] { + [] => "you can `clone` the value and consume it, but \ + this might not be your desired behavior" + .to_string(), + [error] => { + format!( + "you could `clone` the value and consume it, if \ + the `{}` trait bound could be satisfied", + error.obligation.predicate, + ) + } + _ => { + format!( + "you could `clone` the value and consume it, if \ + the following trait bounds could be satisfied: \ + {}", + listify( + &errors, + |e: &FulfillmentError<'tcx>| format!( + "`{}`", + e.obligation.predicate + ) + ) + .unwrap(), + ) + } + }; + err.multipart_suggestion( + msg, + sugg, + Applicability::MaybeIncorrect, + ); + + suggested_cloning = errors.is_empty(); + + for error in errors { + if let FulfillmentErrorCode::Select( + SelectionError::Unimplemented, + ) = error.code + && let ty::PredicateKind::Clause(ty::ClauseKind::Trait( + pred, + )) = error.obligation.predicate.kind().skip_binder() + { + self.infcx.err_ctxt().suggest_derive( + &error.obligation, + err, + error.obligation.predicate.kind().rebind(pred), + ); + } } } } @@ -1558,6 +1596,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }) } } + ExplainCapturesResult { clone_suggestion: suggested_cloning } } /// Skip over locals that begin with an underscore or have no name diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 0d0147e5eb51d..1dfd83956e49b 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -269,14 +269,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .span_delayed_bug(span, "Type may implement copy, but there is no other error."); return; } + + let mut has_clone_suggestion = false; let mut err = match kind { - &IllegalMoveOriginKind::BorrowedContent { target_place } => self - .report_cannot_move_from_borrowed_content( + &IllegalMoveOriginKind::BorrowedContent { target_place } => { + let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content( original_path, target_place, span, use_spans, - ), + ); + has_clone_suggestion = clone_sugg; + diag + } &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { self.cannot_move_out_of_interior_of_drop(span, ty) } @@ -285,7 +290,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } }; - self.add_move_hints(error, &mut err, span); + self.add_move_hints(error, &mut err, span, has_clone_suggestion); self.buffer_error(err); } @@ -426,7 +431,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { deref_target_place: Place<'tcx>, span: Span, use_spans: Option>, - ) -> Diag<'infcx> { + ) -> (Diag<'infcx>, bool) { let tcx = self.infcx.tcx; // Inspect the type of the content behind the // borrow to provide feedback about why this @@ -447,8 +452,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let decl = &self.body.local_decls[local]; let local_name = self.local_name(local).map(|sym| format!("`{sym}`")); if decl.is_ref_for_guard() { - return self - .cannot_move_out_of( + return ( + self.cannot_move_out_of( span, &format!( "{} in pattern guard", @@ -458,9 +463,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .with_note( "variables bound in patterns cannot be moved from \ until after the end of the pattern guard", - ); + ), + false, + ); } else if decl.is_ref_to_static() { - return self.report_cannot_move_from_static(move_place, span); + return (self.report_cannot_move_from_static(move_place, span), false); } } @@ -539,10 +546,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { has_suggest_reborrow: false, maybe_reinitialized_locations_is_empty: true, }; - if let Some(use_spans) = use_spans { - self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt); - } - err + let suggested_cloning = if let Some(use_spans) = use_spans { + self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt) + .clone_suggestion + } else { + false + }; + (err, suggested_cloning) } fn report_closure_move_error( @@ -676,7 +686,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } } - fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span) { + fn add_move_hints( + &self, + error: GroupedMoveError<'tcx>, + err: &mut Diag<'_>, + span: Span, + has_clone_suggestion: bool, + ) { match error { GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { self.add_borrow_suggestions(err, span, !binds_to.is_empty()); @@ -719,14 +735,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { None => "value".to_string(), }; - if let Some(expr) = self.find_expr(use_span) { - self.suggest_cloning( - err, - original_path.as_ref(), - place_ty, - expr, - Some(use_spans), - ); + if !has_clone_suggestion { + if let Some(expr) = self.find_expr(use_span) { + self.suggest_cloning( + err, + original_path.as_ref(), + place_ty, + expr, + Some(use_spans), + ); + } } if let Some(upvar_field) = self diff --git a/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.fixed b/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.fixed index a19db7e5cd32a..8d5ebbc774408 100644 --- a/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.fixed +++ b/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.fixed @@ -2,6 +2,6 @@ use std::rc::Rc; pub fn main() { - let _x = as Clone>::clone(&Rc::new(vec![1, 2]).clone()).into_iter(); + let _x = as Clone>::clone(&Rc::new(vec![1, 2])).into_iter(); //~^ ERROR [E0507] } diff --git a/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.stderr b/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.stderr index 577c2de38be01..076f0ce3440a0 100644 --- a/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.stderr +++ b/tests/ui/borrowck/borrowck-move-out-of-overloaded-auto-deref.stderr @@ -12,10 +12,6 @@ help: you can `clone` the value and consume it, but this might not be your desir | LL | let _x = as Clone>::clone(&Rc::new(vec![1, 2])).into_iter(); | ++++++++++++++++++++++++++++ + -help: consider cloning the value if the performance cost is acceptable - | -LL | let _x = Rc::new(vec![1, 2]).clone().into_iter(); - | ++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/clone-span-on-try-operator.fixed b/tests/ui/borrowck/clone-span-on-try-operator.fixed index 59a162e72c176..86ce083da65e2 100644 --- a/tests/ui/borrowck/clone-span-on-try-operator.fixed +++ b/tests/ui/borrowck/clone-span-on-try-operator.fixed @@ -7,5 +7,5 @@ impl Foo { } fn main() { let foo = &Foo; - ::clone(&foo.clone()).foo(); //~ ERROR cannot move out + foo.clone().foo(); //~ ERROR cannot move out } diff --git a/tests/ui/borrowck/clone-span-on-try-operator.stderr b/tests/ui/borrowck/clone-span-on-try-operator.stderr index c2c63f9494362..4cf1de52a4a47 100644 --- a/tests/ui/borrowck/clone-span-on-try-operator.stderr +++ b/tests/ui/borrowck/clone-span-on-try-operator.stderr @@ -11,10 +11,6 @@ note: `Foo::foo` takes ownership of the receiver `self`, which moves `*foo` | LL | fn foo(self) {} | ^^^^ -help: you can `clone` the value and consume it, but this might not be your desired behavior - | -LL | ::clone(&(*foo)).foo(); - | +++++++++++++++++++++++ + help: consider cloning the value if the performance cost is acceptable | LL - (*foo).foo(); diff --git a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.fixed b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.fixed index 3b4f7c8465cfa..c7ddef0f7bc73 100644 --- a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.fixed +++ b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.fixed @@ -9,7 +9,7 @@ fn call(f: F) where F : Fn() { fn main() { let y = vec![format!("World")]; call(|| { - as Clone>::clone(&y.clone()).into_iter(); + y.clone().into_iter(); //~^ ERROR cannot move out of `y`, a captured variable in an `Fn` closure }); } diff --git a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr index 69c3667491633..08cf528839834 100644 --- a/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr +++ b/tests/ui/borrowck/unboxed-closures-move-upvar-from-non-once-ref-closure.stderr @@ -17,10 +17,6 @@ LL | fn call(f: F) where F : Fn() { | ^^^^ note: `into_iter` takes ownership of the receiver `self`, which moves `y` --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL -help: you can `clone` the value and consume it, but this might not be your desired behavior - | -LL | as Clone>::clone(&y).into_iter(); - | +++++++++++++++++++++++++++++++ + help: consider cloning the value if the performance cost is acceptable | LL | y.clone().into_iter(); diff --git a/tests/ui/moves/suggest-clone.fixed b/tests/ui/moves/suggest-clone.fixed index 59a162e72c176..86ce083da65e2 100644 --- a/tests/ui/moves/suggest-clone.fixed +++ b/tests/ui/moves/suggest-clone.fixed @@ -7,5 +7,5 @@ impl Foo { } fn main() { let foo = &Foo; - ::clone(&foo.clone()).foo(); //~ ERROR cannot move out + foo.clone().foo(); //~ ERROR cannot move out } diff --git a/tests/ui/moves/suggest-clone.stderr b/tests/ui/moves/suggest-clone.stderr index f8e0ccdfceff0..ec18d2abdec0f 100644 --- a/tests/ui/moves/suggest-clone.stderr +++ b/tests/ui/moves/suggest-clone.stderr @@ -11,10 +11,6 @@ note: `Foo::foo` takes ownership of the receiver `self`, which moves `*foo` | LL | fn foo(self) {} | ^^^^ -help: you can `clone` the value and consume it, but this might not be your desired behavior - | -LL | ::clone(&foo).foo(); - | +++++++++++++++++++++++ + help: consider cloning the value if the performance cost is acceptable | LL | foo.clone().foo(); diff --git a/tests/ui/suggestions/option-content-move.fixed b/tests/ui/suggestions/option-content-move.fixed index 0720146280f90..f36c36d7d645f 100644 --- a/tests/ui/suggestions/option-content-move.fixed +++ b/tests/ui/suggestions/option-content-move.fixed @@ -7,7 +7,7 @@ impl LipogramCorpora { pub fn validate_all(&mut self) -> Result<(), char> { for selection in &self.selections { if selection.1.is_some() { - if as Clone>::clone(&selection.1.clone()).as_mut().as_ref().unwrap().contains(selection.0) { + if selection.1.clone().as_mut().as_ref().unwrap().contains(selection.0) { //~^ ERROR cannot move out of `selection.1` return Err(selection.0); } @@ -25,7 +25,7 @@ impl LipogramCorpora2 { pub fn validate_all(&mut self) -> Result<(), char> { for selection in &self.selections { if selection.1.is_ok() { - if as Clone>::clone(&selection.1.clone()).as_mut().as_ref().unwrap().contains(selection.0) { + if selection.1.clone().as_mut().as_ref().unwrap().contains(selection.0) { //~^ ERROR cannot move out of `selection.1` return Err(selection.0); } diff --git a/tests/ui/suggestions/option-content-move.stderr b/tests/ui/suggestions/option-content-move.stderr index 09c6f39fd803d..b514622699e73 100644 --- a/tests/ui/suggestions/option-content-move.stderr +++ b/tests/ui/suggestions/option-content-move.stderr @@ -16,10 +16,6 @@ help: consider calling `.as_mut()` to mutably borrow the value's contents | LL | if selection.1.as_mut().unwrap().contains(selection.0) { | +++++++++ -help: you can `clone` the value and consume it, but this might not be your desired behavior - | -LL | if as Clone>::clone(&selection.1).unwrap().contains(selection.0) { - | ++++++++++++++++++++++++++++++++++ + help: consider cloning the value if the performance cost is acceptable | LL | if selection.1.clone().unwrap().contains(selection.0) { @@ -43,10 +39,6 @@ help: consider calling `.as_mut()` to mutably borrow the value's contents | LL | if selection.1.as_mut().unwrap().contains(selection.0) { | +++++++++ -help: you can `clone` the value and consume it, but this might not be your desired behavior - | -LL | if as Clone>::clone(&selection.1).unwrap().contains(selection.0) { - | ++++++++++++++++++++++++++++++++++++++++++ + help: consider cloning the value if the performance cost is acceptable | LL | if selection.1.clone().unwrap().contains(selection.0) { From 180bfeffdb6cd79c76535286b64789e54602997a Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:08:05 -0500 Subject: [PATCH 5/8] Pass fields to `is_tuple_fields` instead of `SBValue` object --- src/etc/lldb_providers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index ef3f94ee83530..d84807687f2c2 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -266,7 +266,7 @@ def aggregate_field_summary(valobj: SBValue, _dict) -> Generator[str, None, None if summary is None: summary = child.value if summary is None: - if is_tuple_fields(child): + if is_tuple_fields(child.GetType().fields): summary = TupleSummaryProvider(child, _dict) else: summary = StructSummaryProvider(child, _dict) From 2b46d9204a82c50d57b66ba31b8dbbc5bcbd56bc Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:17:17 -0400 Subject: [PATCH 6/8] Improve suggestion for `$`-prefixed fragment specifiers --- compiler/rustc_expand/src/mbe/quoted.rs | 46 +++++++++++++++---- tests/ui/macros/macro-missing-fragment.rs | 4 ++ tests/ui/macros/macro-missing-fragment.stderr | 16 ++++++- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index a698db5437598..92d19820848b4 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -2,6 +2,7 @@ use rustc_ast::token::{self, Delimiter, IdentIsRaw, NonterminalKind, Token}; use rustc_ast::tokenstream::TokenStreamIter; use rustc_ast::{NodeId, tokenstream}; use rustc_ast_pretty::pprust; +use rustc_errors::Applicability; use rustc_feature::Features; use rustc_session::Session; use rustc_session::parse::feature_err; @@ -88,16 +89,17 @@ fn parse( continue; }; - // Push a metavariable with no fragment specifier at the given span - let mut missing_fragment_specifier = |span| { + let fallback_metavar_decl = + |span| TokenTree::MetaVarDecl { span, name: ident, kind: NonterminalKind::TT }; + // Emit a missing-fragment diagnostic and return a `TokenTree` fallback so parsing can + // continue. + let missing_fragment_specifier = |span, add_span| { sess.dcx().emit_err(errors::MissingFragmentSpecifier { span, - add_span: span.shrink_to_hi(), + add_span, valid: VALID_FRAGMENT_NAMES_MSG, }); - - // Fall back to a `TokenTree` since that will match anything if we continue expanding. - result.push(TokenTree::MetaVarDecl { span, name: ident, kind: NonterminalKind::TT }); + fallback_metavar_decl(span) }; // Not consuming the next token immediately, as it may not be a colon @@ -112,13 +114,39 @@ fn parse( // since if it's not a token then it will be an invalid declaration. let Some(tokenstream::TokenTree::Token(token, _)) = iter.next() else { // Invalid, return a nice source location as `var:` - missing_fragment_specifier(colon_span.with_lo(start_sp.lo())); + result.push(missing_fragment_specifier( + colon_span.with_lo(start_sp.lo()), + colon_span.shrink_to_hi(), + )); continue; }; let Some((fragment, _)) = token.ident() else { // No identifier for the fragment specifier; - missing_fragment_specifier(token.span); + if token.kind == token::Dollar + && iter.peek().is_some_and(|next| { + matches!( + next, + tokenstream::TokenTree::Token(next_token, _) + if next_token.ident().is_some() + ) + }) + { + let mut err = + sess.dcx().struct_span_err(token.span, "missing fragment specifier"); + err.note("fragment specifiers must be provided"); + err.help(VALID_FRAGMENT_NAMES_MSG); + err.span_suggestion_verbose( + token.span, + "fragment specifiers should not be prefixed with `$`", + "", + Applicability::MaybeIncorrect, + ); + err.emit(); + result.push(fallback_metavar_decl(token.span)); + } else { + result.push(missing_fragment_specifier(token.span, token.span.shrink_to_hi())); + } continue; }; @@ -146,7 +174,7 @@ fn parse( } else { // Whether it's none or some other tree, it doesn't belong to // the current meta variable, returning the original span. - missing_fragment_specifier(start_sp); + result.push(missing_fragment_specifier(start_sp, start_sp.shrink_to_hi())); } } result diff --git a/tests/ui/macros/macro-missing-fragment.rs b/tests/ui/macros/macro-missing-fragment.rs index 7ed9074020e4b..827c7fc319272 100644 --- a/tests/ui/macros/macro-missing-fragment.rs +++ b/tests/ui/macros/macro-missing-fragment.rs @@ -13,6 +13,10 @@ macro_rules! unused_macro { ( $name ) => {}; //~ ERROR missing fragment } +macro_rules! accidental_dollar_prefix { + ( $test:$tt ) => {}; //~ ERROR missing fragment +} + fn main() { used_arm!(); used_macro_unused_arm!(); diff --git a/tests/ui/macros/macro-missing-fragment.stderr b/tests/ui/macros/macro-missing-fragment.stderr index 886292378d1a0..b1b9bf3d8aa97 100644 --- a/tests/ui/macros/macro-missing-fragment.stderr +++ b/tests/ui/macros/macro-missing-fragment.stderr @@ -37,5 +37,19 @@ help: try adding a specifier here LL | ( $name:spec ) => {}; | +++++ -error: aborting due to 3 previous errors +error: missing fragment specifier + --> $DIR/macro-missing-fragment.rs:17:13 + | +LL | ( $test:$tt ) => {}; + | ^ + | + = note: fragment specifiers must be provided + = help: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis`, along with `expr_2021` and `pat_param` for edition compatibility +help: fragment specifiers should not be prefixed with `$` + | +LL - ( $test:$tt ) => {}; +LL + ( $test:tt ) => {}; + | + +error: aborting due to 4 previous errors From d5b941d163616f12bda3595434414aa3bc7f5d15 Mon Sep 17 00:00:00 2001 From: Apersoma <95072312+Apersoma@users.noreply.github.com> Date: Fri, 24 Apr 2026 23:06:04 +0000 Subject: [PATCH 7/8] added float masks feature --- library/core/src/num/f128.rs | 82 +++++++++++++++++++++++++---- library/core/src/num/f16.rs | 84 ++++++++++++++++++++++++++---- library/core/src/num/f32.rs | 73 ++++++++++++++++++++++---- library/core/src/num/f64.rs | 73 ++++++++++++++++++++++---- library/core/src/num/imp/traits.rs | 12 ++--- 5 files changed, 282 insertions(+), 42 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 68c87b48de94d..24ed84dcb0dc3 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -339,14 +339,78 @@ impl f128 { #[unstable(feature = "float_exact_integer_constants", issue = "152466")] pub const MIN_EXACT_INTEGER: i128 = -Self::MAX_EXACT_INTEGER; - /// Sign bit - pub(crate) const SIGN_MASK: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; + /// The mask of the bit used to encode the sign of an [`f128`]. + /// + /// This bit is set when the sign is negative and unset when the sign is + /// positive. + /// If you only need to check whether a value is positive or negative, + /// [`is_sign_positive`] or [`is_sign_negative`] can be used. + /// + /// [`is_sign_positive`]: f128::is_sign_positive + /// [`is_sign_negative`]: f128::is_sign_negative + /// ```rust + /// #![feature(float_masks)] + /// #![feature(f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// let sign_mask = f128::SIGN_MASK; + /// let a = 1.6552f128; + /// let a_bits = a.to_bits(); + /// + /// assert_eq!(a_bits & sign_mask, 0x0); + /// assert_eq!(f128::from_bits(a_bits ^ sign_mask), -a); + /// assert_eq!(sign_mask, (-0.0f128).to_bits()); + /// # } + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const SIGN_MASK: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; - /// Exponent mask - pub(crate) const EXP_MASK: u128 = 0x7fff_0000_0000_0000_0000_0000_0000_0000; + /// The mask of the bits used to encode the exponent of an [`f128`]. + /// + /// Note that the exponent is stored as a biased value, with a bias of 16383 for `f128`. + /// + /// ```rust + /// #![feature(float_masks)] + /// #![feature(f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// fn get_exp(a: f128) -> i128 { + /// let bias = 16383; + /// let biased = a.to_bits() & f128::EXPONENT_MASK; + /// (biased >> (f128::MANTISSA_DIGITS - 1)).cast_signed() - bias + /// } + /// + /// assert_eq!(get_exp(0.5), -1); + /// assert_eq!(get_exp(1.0), 0); + /// assert_eq!(get_exp(2.0), 1); + /// assert_eq!(get_exp(4.0), 2); + /// # } + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const EXPONENT_MASK: u128 = 0x7fff_0000_0000_0000_0000_0000_0000_0000; - /// Mantissa mask - pub(crate) const MAN_MASK: u128 = 0x0000_ffff_ffff_ffff_ffff_ffff_ffff_ffff; + /// The mask of the bits used to encode the mantissa of an [`f128`]. + /// + /// ```rust + /// #![feature(float_masks)] + /// #![feature(f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// let mantissa_mask = f128::MANTISSA_MASK; + /// + /// assert_eq!(0f128.to_bits() & mantissa_mask, 0x0); + /// assert_eq!(1f128.to_bits() & mantissa_mask, 0x0); + /// + /// // multiplying a finite value by a power of 2 doesn't change its mantissa + /// // unless the result or initial value is not normal. + /// let a = 1.6552f128; + /// let b = 4.0 * a; + /// assert_eq!(a.to_bits() & mantissa_mask, b.to_bits() & mantissa_mask); + /// + /// // The maximum and minimum values have a saturated significand + /// assert_eq!(f128::MAX.to_bits() & f128::MANTISSA_MASK, f128::MANTISSA_MASK); + /// assert_eq!(f128::MIN.to_bits() & f128::MANTISSA_MASK, f128::MANTISSA_MASK); + /// # } + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const MANTISSA_MASK: u128 = 0x0000_ffff_ffff_ffff_ffff_ffff_ffff_ffff; /// Minimum representable positive value (min subnormal) const TINY_BITS: u128 = 0x1; @@ -510,9 +574,9 @@ impl f128 { #[unstable(feature = "f128", issue = "116909")] pub const fn classify(self) -> FpCategory { let bits = self.to_bits(); - match (bits & Self::MAN_MASK, bits & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, + match (bits & Self::MANTISSA_MASK, bits & Self::EXPONENT_MASK) { + (0, Self::EXPONENT_MASK) => FpCategory::Infinite, + (_, Self::EXPONENT_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 3412e49c49cd0..d0cc9580765c0 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -333,14 +333,80 @@ impl f16 { #[unstable(feature = "float_exact_integer_constants", issue = "152466")] pub const MIN_EXACT_INTEGER: i16 = -Self::MAX_EXACT_INTEGER; - /// Sign bit - pub(crate) const SIGN_MASK: u16 = 0x8000; + /// The mask of the bit used to encode the sign of an [`f16`]. + /// + /// This bit is set when the sign is negative and unset when the sign is + /// positive. + /// If you only need to check whether a value is positive or negative, + /// [`is_sign_positive`] or [`is_sign_negative`] can be used. + /// + /// [`is_sign_positive`]: f16::is_sign_positive + /// [`is_sign_negative`]: f16::is_sign_negative + /// ```rust + /// #![feature(float_masks)] + /// #![feature(f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// let sign_mask = f16::SIGN_MASK; + /// let a = 1.6552f16; + /// let a_bits = a.to_bits(); + /// + /// assert_eq!(a_bits & sign_mask, 0x0); + /// assert_eq!(f16::from_bits(a_bits ^ sign_mask), -a); + /// assert_eq!(sign_mask, (-0.0f16).to_bits()); + /// # } + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const SIGN_MASK: u16 = 0x8000; - /// Exponent mask - pub(crate) const EXP_MASK: u16 = 0x7c00; + /// The mask of the bits used to encode the exponent of an [`f16`]. + /// + /// Note that the exponent is stored as a biased value, with a bias of 15 for `f16`. + /// + /// ```rust + /// #![feature(float_masks)] + /// #![feature(f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// let exponent_mask = f16::EXPONENT_MASK; + /// + /// fn get_exp(a: f16) -> i16 { + /// let bias = 15; + /// let biased = a.to_bits() & f16::EXPONENT_MASK; + /// (biased >> (f16::MANTISSA_DIGITS - 1)).cast_signed() - bias + /// } + /// + /// assert_eq!(get_exp(0.5), -1); + /// assert_eq!(get_exp(1.0), 0); + /// assert_eq!(get_exp(2.0), 1); + /// assert_eq!(get_exp(4.0), 2); + /// # } + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const EXPONENT_MASK: u16 = 0x7c00; - /// Mantissa mask - pub(crate) const MAN_MASK: u16 = 0x03ff; + /// The mask of the bits used to encode the mantissa of an [`f16`]. + /// + /// ```rust + /// #![feature(float_masks)] + /// #![feature(f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// let mantissa_mask = f16::MANTISSA_MASK; + /// + /// assert_eq!(0f16.to_bits() & mantissa_mask, 0x0); + /// assert_eq!(1f16.to_bits() & mantissa_mask, 0x0); + /// + /// // multiplying a finite value by a power of 2 doesn't change its mantissa + /// // unless the result or initial value is not normal. + /// let a = 1.6552f16; + /// let b = 4.0 * a; + /// assert_eq!(a.to_bits() & mantissa_mask, b.to_bits() & mantissa_mask); + /// + /// // The maximum and minimum values have a saturated significand + /// assert_eq!(f16::MAX.to_bits() & f16::MANTISSA_MASK, f16::MANTISSA_MASK); + /// assert_eq!(f16::MIN.to_bits() & f16::MANTISSA_MASK, f16::MANTISSA_MASK); + /// # } + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const MANTISSA_MASK: u16 = 0x03ff; /// Minimum representable positive value (min subnormal) const TINY_BITS: u16 = 0x1; @@ -502,9 +568,9 @@ impl f16 { #[unstable(feature = "f16", issue = "116909")] pub const fn classify(self) -> FpCategory { let b = self.to_bits(); - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, + match (b & Self::MANTISSA_MASK, b & Self::EXPONENT_MASK) { + (0, Self::EXPONENT_MASK) => FpCategory::Infinite, + (_, Self::EXPONENT_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index e33cb098e4e8d..0a80e19f51a5e 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -572,14 +572,69 @@ impl f32 { #[unstable(feature = "float_exact_integer_constants", issue = "152466")] pub const MIN_EXACT_INTEGER: i32 = -Self::MAX_EXACT_INTEGER; - /// Sign bit - pub(crate) const SIGN_MASK: u32 = 0x8000_0000; + /// The mask of the bit used to encode the sign of an [`f32`]. + /// + /// This bit is set when the sign is negative and unset when the sign is + /// positive. + /// If you only need to check whether a value is positive or negative, + /// [`is_sign_positive`] or [`is_sign_negative`] can be used. + /// + /// [`is_sign_positive`]: f32::is_sign_positive + /// [`is_sign_negative`]: f32::is_sign_negative + /// ```rust + /// #![feature(float_masks)] + /// let sign_mask = f32::SIGN_MASK; + /// let a = 1.6552f32; + /// let a_bits = a.to_bits(); + /// + /// assert_eq!(a_bits & sign_mask, 0x0); + /// assert_eq!(f32::from_bits(a_bits ^ sign_mask), -a); + /// assert_eq!(sign_mask, (-0.0f32).to_bits()); + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const SIGN_MASK: u32 = 0x8000_0000; - /// Exponent mask - pub(crate) const EXP_MASK: u32 = 0x7f80_0000; + /// The mask of the bits used to encode the exponent of an [`f32`]. + /// + /// Note that the exponent is stored as a biased value, with a bias of 127 for `f32`. + /// + /// ```rust + /// #![feature(float_masks)] + /// fn get_exp(a: f32) -> i32 { + /// let bias = 127; + /// let biased = a.to_bits() & f32::EXPONENT_MASK; + /// (biased >> (f32::MANTISSA_DIGITS - 1)).cast_signed() - bias + /// } + /// + /// assert_eq!(get_exp(0.5), -1); + /// assert_eq!(get_exp(1.0), 0); + /// assert_eq!(get_exp(2.0), 1); + /// assert_eq!(get_exp(4.0), 2); + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const EXPONENT_MASK: u32 = 0x7f80_0000; - /// Mantissa mask - pub(crate) const MAN_MASK: u32 = 0x007f_ffff; + /// The mask of the bits used to encode the mantissa of an [`f32`]. + /// + /// ```rust + /// #![feature(float_masks)] + /// let mantissa_mask = f32::MANTISSA_MASK; + /// + /// assert_eq!(0f32.to_bits() & mantissa_mask, 0x0); + /// assert_eq!(1f32.to_bits() & mantissa_mask, 0x0); + /// + /// // multiplying a finite value by a power of 2 doesn't change its mantissa + /// // unless the result or initial value is not normal. + /// let a = 1.6552f32; + /// let b = 4.0 * a; + /// assert_eq!(a.to_bits() & mantissa_mask, b.to_bits() & mantissa_mask); + /// + /// // The maximum and minimum values have a saturated significand + /// assert_eq!(f32::MAX.to_bits() & f32::MANTISSA_MASK, f32::MANTISSA_MASK); + /// assert_eq!(f32::MIN.to_bits() & f32::MANTISSA_MASK, f32::MANTISSA_MASK); + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const MANTISSA_MASK: u32 = 0x007f_ffff; /// Minimum representable positive value (min subnormal) const TINY_BITS: u32 = 0x1; @@ -730,9 +785,9 @@ impl f32 { // of our tests is able to find any difference between the complicated and the naive // version, so now we are back to the naive version. let b = self.to_bits(); - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, + match (b & Self::MANTISSA_MASK, b & Self::EXPONENT_MASK) { + (0, Self::EXPONENT_MASK) => FpCategory::Infinite, + (_, Self::EXPONENT_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 872f567efafdc..4d57f1dab8524 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -571,14 +571,69 @@ impl f64 { #[unstable(feature = "float_exact_integer_constants", issue = "152466")] pub const MIN_EXACT_INTEGER: i64 = -Self::MAX_EXACT_INTEGER; - /// Sign bit - pub(crate) const SIGN_MASK: u64 = 0x8000_0000_0000_0000; + /// The mask of the bit used to encode the sign of an [`f64`]. + /// + /// This bit is set when the sign is negative and unset when the sign is + /// positive. + /// If you only need to check whether a value is positive or negative, + /// [`is_sign_positive`] or [`is_sign_negative`] can be used. + /// + /// [`is_sign_positive`]: f64::is_sign_positive + /// [`is_sign_negative`]: f64::is_sign_negative + /// ```rust + /// #![feature(float_masks)] + /// let sign_mask = f64::SIGN_MASK; + /// let a = 1.6552f64; + /// let a_bits = a.to_bits(); + /// + /// assert_eq!(a_bits & sign_mask, 0x0); + /// assert_eq!(f64::from_bits(a_bits ^ sign_mask), -a); + /// assert_eq!(sign_mask, (-0.0f64).to_bits()); + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const SIGN_MASK: u64 = 0x8000_0000_0000_0000; - /// Exponent mask - pub(crate) const EXP_MASK: u64 = 0x7ff0_0000_0000_0000; + /// The mask of the bits used to encode the exponent of an [`f64`]. + /// + /// Note that the exponent is stored as a biased value, with a bias of 1024 for `f64`. + /// + /// ```rust + /// #![feature(float_masks)] + /// fn get_exp(a: f64) -> i64 { + /// let bias = 1023; + /// let biased = a.to_bits() & f64::EXPONENT_MASK; + /// (biased >> (f64::MANTISSA_DIGITS - 1)).cast_signed() - bias + /// } + /// + /// assert_eq!(get_exp(0.5), -1); + /// assert_eq!(get_exp(1.0), 0); + /// assert_eq!(get_exp(2.0), 1); + /// assert_eq!(get_exp(4.0), 2); + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const EXPONENT_MASK: u64 = 0x7ff0_0000_0000_0000; - /// Mantissa mask - pub(crate) const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff; + /// The mask of the bits used to encode the mantissa of an [`f64`]. + /// + /// ```rust + /// #![feature(float_masks)] + /// let mantissa_mask = f64::MANTISSA_MASK; + /// + /// assert_eq!(0f64.to_bits() & mantissa_mask, 0x0); + /// assert_eq!(1f64.to_bits() & mantissa_mask, 0x0); + /// + /// // multiplying a finite value by a power of 2 doesn't change its mantissa + /// // unless the result or initial value is not normal. + /// let a = 1.6552f64; + /// let b = 4.0 * a; + /// assert_eq!(a.to_bits() & mantissa_mask, b.to_bits() & mantissa_mask); + /// + /// // The maximum and minimum values have a saturated significand + /// assert_eq!(f64::MAX.to_bits() & f64::MANTISSA_MASK, f64::MANTISSA_MASK); + /// assert_eq!(f64::MIN.to_bits() & f64::MANTISSA_MASK, f64::MANTISSA_MASK); + /// ``` + #[unstable(feature = "float_masks", issue = "154064")] + pub const MANTISSA_MASK: u64 = 0x000f_ffff_ffff_ffff; /// Minimum representable positive value (min subnormal) const TINY_BITS: u64 = 0x1; @@ -729,9 +784,9 @@ impl f64 { // of our tests is able to find any difference between the complicated and the naive // version, so now we are back to the naive version. let b = self.to_bits(); - match (b & Self::MAN_MASK, b & Self::EXP_MASK) { - (0, Self::EXP_MASK) => FpCategory::Infinite, - (_, Self::EXP_MASK) => FpCategory::Nan, + match (b & Self::MANTISSA_MASK, b & Self::EXPONENT_MASK) { + (0, Self::EXPONENT_MASK) => FpCategory::Infinite, + (_, Self::EXPONENT_MASK) => FpCategory::Nan, (0, 0) => FpCategory::Zero, (_, 0) => FpCategory::Subnormal, _ => FpCategory::Normal, diff --git a/library/core/src/num/imp/traits.rs b/library/core/src/num/imp/traits.rs index 7b84f7a4a5aa2..3a7ea23f73f31 100644 --- a/library/core/src/num/imp/traits.rs +++ b/library/core/src/num/imp/traits.rs @@ -204,8 +204,8 @@ impl Float for f16 { const BITS: u32 = 16; const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS; - const EXP_MASK: Self::Int = Self::EXP_MASK; - const SIG_MASK: Self::Int = Self::MAN_MASK; + const EXP_MASK: Self::Int = Self::EXPONENT_MASK; + const SIG_MASK: Self::Int = Self::MANTISSA_MASK; const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -22; const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 5; @@ -238,8 +238,8 @@ impl Float for f32 { const BITS: u32 = 32; const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS; - const EXP_MASK: Self::Int = Self::EXP_MASK; - const SIG_MASK: Self::Int = Self::MAN_MASK; + const EXP_MASK: Self::Int = Self::EXPONENT_MASK; + const SIG_MASK: Self::Int = Self::MANTISSA_MASK; const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -17; const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 10; @@ -271,8 +271,8 @@ impl Float for f64 { const BITS: u32 = 64; const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS; - const EXP_MASK: Self::Int = Self::EXP_MASK; - const SIG_MASK: Self::Int = Self::MAN_MASK; + const EXP_MASK: Self::Int = Self::EXPONENT_MASK; + const SIG_MASK: Self::Int = Self::MANTISSA_MASK; const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -4; const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 23; From 61ff157bd47ce626795f1672d6cc7bf3ba292fd6 Mon Sep 17 00:00:00 2001 From: yuk1ty Date: Sat, 11 Apr 2026 21:28:03 +0900 Subject: [PATCH 8/8] Address custom type implementing Derefs to suggest UFCS clone --- .../src/diagnostics/conflict_errors.rs | 4 +- .../rustc_borrowck/src/diagnostics/mod.rs | 22 ++-- .../src/diagnostics/move_errors.rs | 101 +++++++++++++++--- .../ufcs-for-deref-inner-clone.fixed | 16 +++ .../suggestions/ufcs-for-deref-inner-clone.rs | 16 +++ .../ufcs-for-deref-inner-clone.stderr | 14 +++ 6 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 tests/ui/suggestions/ufcs-for-deref-inner-clone.fixed create mode 100644 tests/ui/suggestions/ufcs-for-deref-inner-clone.rs create mode 100644 tests/ui/suggestions/ufcs-for-deref-inner-clone.stderr diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 639cb75fdd28b..2735c800c4a38 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -254,9 +254,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { maybe_reinitialized_locations_is_empty: maybe_reinitialized_locations .is_empty(), }; - // The return value indicates whether a clone suggestion was emitted; - // redundancy is already handled via the `FnSelfUse` check below. - let _ = self.explain_captures( + self.explain_captures( &mut err, span, move_span, diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 3ee0369c9da6e..ab2c3bdba85f5 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -602,8 +602,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { BorrowedContentSource::DerefRawPointer } else if base_ty.is_mutable_ptr() { BorrowedContentSource::DerefMutableRef - } else { + } else if base_ty.is_ref() { BorrowedContentSource::DerefSharedRef + } else { + // Custom type implementing `Deref` (e.g. `MyBox`, `Rc`, `Arc`) + // that wasn't detected via the MIR init trace above. This can happen + // when the deref base is initialized by a regular statement rather than + // a `TerminatorKind::Call` with `CallSource::OverloadedOperator`. + BorrowedContentSource::OverloadedDeref(base_ty) } } @@ -1002,10 +1008,12 @@ struct CapturedMessageOpt { maybe_reinitialized_locations_is_empty: bool, } -/// Tracks which suggestions were emitted by [`MirBorrowckCtxt::explain_captures`], -/// so callers can avoid emitting redundant suggestions downstream. -struct ExplainCapturesResult { - clone_suggestion: bool, +/// Tracks whether [`MirBorrowckCtxt::explain_captures`] emitted a clone +/// suggestion, so callers can avoid emitting redundant suggestions downstream. +#[derive(Copy, Clone, PartialEq, Eq)] +pub(super) enum CloneSuggestion { + Emitted, + NotEmitted, } impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { @@ -1232,7 +1240,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { move_spans: UseSpans<'tcx>, moved_place: Place<'tcx>, msg_opt: CapturedMessageOpt, - ) -> ExplainCapturesResult { + ) -> CloneSuggestion { let CapturedMessageOpt { is_partial_move: is_partial, is_loop_message, @@ -1596,7 +1604,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }) } } - ExplainCapturesResult { clone_suggestion: suggested_cloning } + if suggested_cloning { CloneSuggestion::Emitted } else { CloneSuggestion::NotEmitted } } /// Skip over locals that begin with an underscore or have no name diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 1dfd83956e49b..9aa0bc1b745d6 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -14,7 +14,9 @@ use rustc_trait_selection::infer::InferCtxtExt; use tracing::debug; use crate::MirBorrowckCtxt; -use crate::diagnostics::{CapturedMessageOpt, DescribePlaceOpt, UseSpans}; +use crate::diagnostics::{ + BorrowedContentSource, CapturedMessageOpt, CloneSuggestion, DescribePlaceOpt, UseSpans, +}; use crate::prefixes::PrefixSet; #[derive(Debug)] @@ -270,7 +272,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { return; } - let mut has_clone_suggestion = false; + let mut has_clone_suggestion = CloneSuggestion::NotEmitted; let mut err = match kind { &IllegalMoveOriginKind::BorrowedContent { target_place } => { let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content( @@ -431,7 +433,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { deref_target_place: Place<'tcx>, span: Span, use_spans: Option>, - ) -> (Diag<'infcx>, bool) { + ) -> (Diag<'infcx>, CloneSuggestion) { let tcx = self.infcx.tcx; // Inspect the type of the content behind the // borrow to provide feedback about why this @@ -464,10 +466,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { "variables bound in patterns cannot be moved from \ until after the end of the pattern guard", ), - false, + CloneSuggestion::NotEmitted, ); } else if decl.is_ref_to_static() { - return (self.report_cannot_move_from_static(move_place, span), false); + return ( + self.report_cannot_move_from_static(move_place, span), + CloneSuggestion::NotEmitted, + ); } } @@ -548,9 +553,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }; let suggested_cloning = if let Some(use_spans) = use_spans { self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt) - .clone_suggestion } else { - false + CloneSuggestion::NotEmitted }; (err, suggested_cloning) } @@ -686,12 +690,48 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } } + /// Suggest cloning via UFCS when a move occurs through a custom `Deref` impl. + /// + /// A simple `.clone()` on a type like `MyBox>` would clone the wrapper, + /// not the inner `Vec`. Instead, we suggest ` as Clone>::clone(&val)`. + fn suggest_cloning_through_overloaded_deref( + &self, + err: &mut Diag<'_>, + ty: Ty<'tcx>, + span: Span, + ) -> CloneSuggestion { + let tcx = self.infcx.tcx; + let Some(clone_trait) = tcx.lang_items().clone_trait() else { + return CloneSuggestion::NotEmitted; + }; + let Some(errors) = + self.infcx.type_implements_trait_shallow(clone_trait, ty, self.infcx.param_env) + else { + return CloneSuggestion::NotEmitted; + }; + + if !errors.is_empty() { + return CloneSuggestion::NotEmitted; + } + let sugg = vec![ + (span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")), + (span.shrink_to_hi(), ")".to_string()), + ]; + err.multipart_suggestion( + "you can `clone` the value and consume it, but this might not be \ + your desired behavior", + sugg, + Applicability::MaybeIncorrect, + ); + CloneSuggestion::Emitted + } + fn add_move_hints( &self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span, - has_clone_suggestion: bool, + has_clone_suggestion: CloneSuggestion, ) { match error { GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { @@ -735,15 +775,42 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { None => "value".to_string(), }; - if !has_clone_suggestion { - if let Some(expr) = self.find_expr(use_span) { - self.suggest_cloning( - err, - original_path.as_ref(), - place_ty, - expr, - Some(use_spans), - ); + if has_clone_suggestion == CloneSuggestion::NotEmitted { + // Check if the move is directly through a custom Deref impl + // (e.g. `*my_box` where MyBox implements Deref). + // A simple `.clone()` would clone the wrapper type rather than + // the inner value, so we need a UFCS suggestion instead. + // + // However, if there are further projections after the deref + // (e.g. `(*rc).field`), the value accessed is already the inner + // type and a simple `.clone()` works correctly. + let needs_ufcs = original_path.projection.last() + == Some(&ProjectionElem::Deref) + && original_path.iter_projections().any(|(place, elem)| { + matches!(elem, ProjectionElem::Deref) + && matches!( + self.borrowed_content_source(place), + BorrowedContentSource::OverloadedDeref(_) + | BorrowedContentSource::OverloadedIndex(_) + ) + }); + + let emitted_ufcs = if needs_ufcs { + self.suggest_cloning_through_overloaded_deref(err, place_ty, use_span) + } else { + CloneSuggestion::NotEmitted + }; + + if emitted_ufcs == CloneSuggestion::NotEmitted { + if let Some(expr) = self.find_expr(use_span) { + self.suggest_cloning( + err, + original_path.as_ref(), + place_ty, + expr, + Some(use_spans), + ); + } } } diff --git a/tests/ui/suggestions/ufcs-for-deref-inner-clone.fixed b/tests/ui/suggestions/ufcs-for-deref-inner-clone.fixed new file mode 100644 index 0000000000000..de85216e8880c --- /dev/null +++ b/tests/ui/suggestions/ufcs-for-deref-inner-clone.fixed @@ -0,0 +1,16 @@ +//@ run-rustfix +use std::ops::Deref; + +struct MyBox(T); + +impl Deref for MyBox { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub fn main() { + let _x = as Clone>::clone(&MyBox(vec![1, 2])).into_iter(); + //~^ ERROR cannot move out of dereference of `MyBox>` +} diff --git a/tests/ui/suggestions/ufcs-for-deref-inner-clone.rs b/tests/ui/suggestions/ufcs-for-deref-inner-clone.rs new file mode 100644 index 0000000000000..dc0dd02f97a92 --- /dev/null +++ b/tests/ui/suggestions/ufcs-for-deref-inner-clone.rs @@ -0,0 +1,16 @@ +//@ run-rustfix +use std::ops::Deref; + +struct MyBox(T); + +impl Deref for MyBox { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub fn main() { + let _x = MyBox(vec![1, 2]).into_iter(); + //~^ ERROR cannot move out of dereference of `MyBox>` +} diff --git a/tests/ui/suggestions/ufcs-for-deref-inner-clone.stderr b/tests/ui/suggestions/ufcs-for-deref-inner-clone.stderr new file mode 100644 index 0000000000000..7fde14f1a61bb --- /dev/null +++ b/tests/ui/suggestions/ufcs-for-deref-inner-clone.stderr @@ -0,0 +1,14 @@ +error[E0507]: cannot move out of dereference of `MyBox>` + --> $DIR/ufcs-for-deref-inner-clone.rs:14:14 + | +LL | let _x = MyBox(vec![1, 2]).into_iter(); + | ^^^^^^^^^^^^^^^^^ move occurs because value has type `Vec`, which does not implement the `Copy` trait + | +help: you can `clone` the value and consume it, but this might not be your desired behavior + | +LL | let _x = as Clone>::clone(&MyBox(vec![1, 2])).into_iter(); + | ++++++++++++++++++++++++++++ + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0507`.