Skip to content

Commit f89abef

Browse files
committed
Auto merge of #122667 - matthiaskrgr:rollup-txkftb6, r=matthiaskrgr
Rollup of 5 pull requests Successful merges: - #121652 (Detect when move of !Copy value occurs within loop and should likely not be cloned) - #122639 (Fix typos) - #122645 (Remove some only- clauses from mir-opt tests) - #122654 (interpret/memory: explain why we use == on bool) - #122656 (simplify_cfg: rename some passes so that they make more sense) r? `@ghost` `@rustbot` modify labels: rollup
2 parents eb45c84 + a6e0fb4 commit f89abef

File tree

52 files changed

+669
-97
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+669
-97
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+256-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxIndexSet;
99
use rustc_errors::{codes::*, struct_span_code_err, Applicability, Diag, MultiSpan};
1010
use rustc_hir as hir;
1111
use rustc_hir::def::{DefKind, Res};
12-
use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
12+
use rustc_hir::intravisit::{walk_block, walk_expr, Map, Visitor};
1313
use rustc_hir::{CoroutineDesugaring, PatField};
1414
use rustc_hir::{CoroutineKind, CoroutineSource, LangItem};
1515
use rustc_middle::hir::nested_filter::OnlyBodies;
@@ -447,8 +447,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
447447
err.span_note(
448448
span,
449449
format!(
450-
"consider changing this parameter type in {descr} `{ident}` to \
451-
borrow instead if owning the value isn't necessary",
450+
"consider changing this parameter type in {descr} `{ident}` to borrow \
451+
instead if owning the value isn't necessary",
452452
),
453453
);
454454
}
@@ -463,7 +463,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
463463
} else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans
464464
{
465465
// We already suggest cloning for these cases in `explain_captures`.
466-
} else {
466+
} else if self.suggest_hoisting_call_outside_loop(err, expr) {
467+
// The place where the the type moves would be misleading to suggest clone.
468+
// #121466
467469
self.suggest_cloning(err, ty, expr, move_span);
468470
}
469471
}
@@ -747,6 +749,234 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
747749
true
748750
}
749751

752+
/// In a move error that occurs on a call wihtin a loop, we try to identify cases where cloning
753+
/// the value would lead to a logic error. We infer these cases by seeing if the moved value is
754+
/// part of the logic to break the loop, either through an explicit `break` or if the expression
755+
/// is part of a `while let`.
756+
fn suggest_hoisting_call_outside_loop(&self, err: &mut Diag<'_>, expr: &hir::Expr<'_>) -> bool {
757+
let tcx = self.infcx.tcx;
758+
let mut can_suggest_clone = true;
759+
760+
// If the moved value is a locally declared binding, we'll look upwards on the expression
761+
// tree until the scope where it is defined, and no further, as suggesting to move the
762+
// expression beyond that point would be illogical.
763+
let local_hir_id = if let hir::ExprKind::Path(hir::QPath::Resolved(
764+
_,
765+
hir::Path { res: hir::def::Res::Local(local_hir_id), .. },
766+
)) = expr.kind
767+
{
768+
Some(local_hir_id)
769+
} else {
770+
// This case would be if the moved value comes from an argument binding, we'll just
771+
// look within the entire item, that's fine.
772+
None
773+
};
774+
775+
/// This will allow us to look for a specific `HirId`, in our case `local_hir_id` where the
776+
/// binding was declared, within any other expression. We'll use it to search for the
777+
/// binding declaration within every scope we inspect.
778+
struct Finder {
779+
hir_id: hir::HirId,
780+
found: bool,
781+
}
782+
impl<'hir> Visitor<'hir> for Finder {
783+
fn visit_pat(&mut self, pat: &'hir hir::Pat<'hir>) {
784+
if pat.hir_id == self.hir_id {
785+
self.found = true;
786+
}
787+
hir::intravisit::walk_pat(self, pat);
788+
}
789+
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
790+
if ex.hir_id == self.hir_id {
791+
self.found = true;
792+
}
793+
hir::intravisit::walk_expr(self, ex);
794+
}
795+
}
796+
// The immediate HIR parent of the moved expression. We'll look for it to be a call.
797+
let mut parent = None;
798+
// The top-most loop where the moved expression could be moved to a new binding.
799+
let mut outer_most_loop: Option<&hir::Expr<'_>> = None;
800+
for (_, node) in tcx.hir().parent_iter(expr.hir_id) {
801+
let e = match node {
802+
hir::Node::Expr(e) => e,
803+
hir::Node::Local(hir::Local { els: Some(els), .. }) => {
804+
let mut finder = BreakFinder { found_breaks: vec![], found_continues: vec![] };
805+
finder.visit_block(els);
806+
if !finder.found_breaks.is_empty() {
807+
// Don't suggest clone as it could be will likely end in an infinite
808+
// loop.
809+
// let Some(_) = foo(non_copy.clone()) else { break; }
810+
// --- ^^^^^^^^ -----
811+
can_suggest_clone = false;
812+
}
813+
continue;
814+
}
815+
_ => continue,
816+
};
817+
if let Some(&hir_id) = local_hir_id {
818+
let mut finder = Finder { hir_id, found: false };
819+
finder.visit_expr(e);
820+
if finder.found {
821+
// The current scope includes the declaration of the binding we're accessing, we
822+
// can't look up any further for loops.
823+
break;
824+
}
825+
}
826+
if parent.is_none() {
827+
parent = Some(e);
828+
}
829+
match e.kind {
830+
hir::ExprKind::Let(_) => {
831+
match tcx.parent_hir_node(e.hir_id) {
832+
hir::Node::Expr(hir::Expr {
833+
kind: hir::ExprKind::If(cond, ..), ..
834+
}) => {
835+
let mut finder = Finder { hir_id: expr.hir_id, found: false };
836+
finder.visit_expr(cond);
837+
if finder.found {
838+
// The expression where the move error happened is in a `while let`
839+
// condition Don't suggest clone as it will likely end in an
840+
// infinite loop.
841+
// while let Some(_) = foo(non_copy.clone()) { }
842+
// --------- ^^^^^^^^
843+
can_suggest_clone = false;
844+
}
845+
}
846+
_ => {}
847+
}
848+
}
849+
hir::ExprKind::Loop(..) => {
850+
outer_most_loop = Some(e);
851+
}
852+
_ => {}
853+
}
854+
}
855+
let loop_count: usize = tcx
856+
.hir()
857+
.parent_iter(expr.hir_id)
858+
.map(|(_, node)| match node {
859+
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Loop(..), .. }) => 1,
860+
_ => 0,
861+
})
862+
.sum();
863+
864+
let sm = tcx.sess.source_map();
865+
if let Some(in_loop) = outer_most_loop {
866+
let mut finder = BreakFinder { found_breaks: vec![], found_continues: vec![] };
867+
finder.visit_expr(in_loop);
868+
// All of the spans for `break` and `continue` expressions.
869+
let spans = finder
870+
.found_breaks
871+
.iter()
872+
.chain(finder.found_continues.iter())
873+
.map(|(_, span)| *span)
874+
.filter(|span| {
875+
!matches!(
876+
span.desugaring_kind(),
877+
Some(DesugaringKind::ForLoop | DesugaringKind::WhileLoop)
878+
)
879+
})
880+
.collect::<Vec<Span>>();
881+
// All of the spans for the loops above the expression with the move error.
882+
let loop_spans: Vec<_> = tcx
883+
.hir()
884+
.parent_iter(expr.hir_id)
885+
.filter_map(|(_, node)| match node {
886+
hir::Node::Expr(hir::Expr { span, kind: hir::ExprKind::Loop(..), .. }) => {
887+
Some(*span)
888+
}
889+
_ => None,
890+
})
891+
.collect();
892+
// It is possible that a user written `break` or `continue` is in the wrong place. We
893+
// point them out at the user for them to make a determination. (#92531)
894+
if !spans.is_empty() && loop_count > 1 {
895+
// Getting fancy: if the spans of the loops *do not* overlap, we only use the line
896+
// number when referring to them. If there *are* overlaps (multiple loops on the
897+
// same line) then we use the more verbose span output (`file.rs:col:ll`).
898+
let mut lines: Vec<_> =
899+
loop_spans.iter().map(|sp| sm.lookup_char_pos(sp.lo()).line).collect();
900+
lines.sort();
901+
lines.dedup();
902+
let fmt_span = |span: Span| {
903+
if lines.len() == loop_spans.len() {
904+
format!("line {}", sm.lookup_char_pos(span.lo()).line)
905+
} else {
906+
sm.span_to_diagnostic_string(span)
907+
}
908+
};
909+
let mut spans: MultiSpan = spans.clone().into();
910+
// Point at all the `continue`s and explicit `break`s in the relevant loops.
911+
for (desc, elements) in [
912+
("`break` exits", &finder.found_breaks),
913+
("`continue` advances", &finder.found_continues),
914+
] {
915+
for (destination, sp) in elements {
916+
if let Ok(hir_id) = destination.target_id
917+
&& let hir::Node::Expr(expr) = tcx.hir().hir_node(hir_id)
918+
&& !matches!(
919+
sp.desugaring_kind(),
920+
Some(DesugaringKind::ForLoop | DesugaringKind::WhileLoop)
921+
)
922+
{
923+
spans.push_span_label(
924+
*sp,
925+
format!("this {desc} the loop at {}", fmt_span(expr.span)),
926+
);
927+
}
928+
}
929+
}
930+
// Point at all the loops that are between this move and the parent item.
931+
for span in loop_spans {
932+
spans.push_span_label(sm.guess_head_span(span), "");
933+
}
934+
935+
// note: verify that your loop breaking logic is correct
936+
// --> $DIR/nested-loop-moved-value-wrong-continue.rs:41:17
937+
// |
938+
// 28 | for foo in foos {
939+
// | ---------------
940+
// ...
941+
// 33 | for bar in &bars {
942+
// | ----------------
943+
// ...
944+
// 41 | continue;
945+
// | ^^^^^^^^ this `continue` advances the loop at line 33
946+
err.span_note(spans, "verify that your loop breaking logic is correct");
947+
}
948+
if let Some(parent) = parent
949+
&& let hir::ExprKind::MethodCall(..) | hir::ExprKind::Call(..) = parent.kind
950+
{
951+
// FIXME: We could check that the call's *parent* takes `&mut val` to make the
952+
// suggestion more targeted to the `mk_iter(val).next()` case. Maybe do that only to
953+
// check for wheter to suggest `let value` or `let mut value`.
954+
955+
let span = in_loop.span;
956+
if !finder.found_breaks.is_empty()
957+
&& let Ok(value) = sm.span_to_snippet(parent.span)
958+
{
959+
// We know with high certainty that this move would affect the early return of a
960+
// loop, so we suggest moving the expression with the move out of the loop.
961+
let indent = if let Some(indent) = sm.indentation_before(span) {
962+
format!("\n{indent}")
963+
} else {
964+
" ".to_string()
965+
};
966+
err.multipart_suggestion(
967+
"consider moving the expression out of the loop so it is only moved once",
968+
vec![
969+
(parent.span, "value".to_string()),
970+
(span.shrink_to_lo(), format!("let mut value = {value};{indent}")),
971+
],
972+
Applicability::MaybeIncorrect,
973+
);
974+
}
975+
}
976+
}
977+
can_suggest_clone
978+
}
979+
750980
fn suggest_cloning(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, expr: &hir::Expr<'_>, span: Span) {
751981
let tcx = self.infcx.tcx;
752982
// Try to find predicates on *generic params* that would allow copying `ty`
@@ -3688,6 +3918,28 @@ impl<'a, 'v> Visitor<'v> for ReferencedStatementsVisitor<'a> {
36883918
}
36893919
}
36903920

3921+
/// Look for `break` expressions within any arbitrary expressions. We'll do this to infer
3922+
/// whether this is a case where the moved value would affect the exit of a loop, making it
3923+
/// unsuitable for a `.clone()` suggestion.
3924+
struct BreakFinder {
3925+
found_breaks: Vec<(hir::Destination, Span)>,
3926+
found_continues: Vec<(hir::Destination, Span)>,
3927+
}
3928+
impl<'hir> Visitor<'hir> for BreakFinder {
3929+
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
3930+
match ex.kind {
3931+
hir::ExprKind::Break(destination, _) => {
3932+
self.found_breaks.push((destination, ex.span));
3933+
}
3934+
hir::ExprKind::Continue(destination) => {
3935+
self.found_continues.push((destination, ex.span));
3936+
}
3937+
_ => {}
3938+
}
3939+
hir::intravisit::walk_expr(self, ex);
3940+
}
3941+
}
3942+
36913943
/// Given a set of spans representing statements initializing the relevant binding, visit all the
36923944
/// function expressions looking for branching code paths that *do not* initialize the binding.
36933945
struct ConditionVisitor<'b> {

compiler/rustc_const_eval/src/interpret/memory.rs

+2
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
949949
/// Runs the close in "validation" mode, which means the machine's memory read hooks will be
950950
/// suppressed. Needless to say, this must only be set with great care! Cannot be nested.
951951
pub(super) fn run_for_validation<R>(&self, f: impl FnOnce() -> R) -> R {
952+
// This deliberately uses `==` on `bool` to follow the pattern
953+
// `assert!(val.replace(new) == old)`.
952954
assert!(
953955
self.memory.validation_in_progress.replace(true) == false,
954956
"`validation_in_progress` was already set"

compiler/rustc_hir/src/hir.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2049,7 +2049,7 @@ impl LoopSource {
20492049
}
20502050
}
20512051

2052-
#[derive(Copy, Clone, Debug, HashStable_Generic)]
2052+
#[derive(Copy, Clone, Debug, PartialEq, HashStable_Generic)]
20532053
pub enum LoopIdError {
20542054
OutsideLoopScope,
20552055
UnlabeledCfInWhileCondition,

compiler/rustc_hir_typeck/src/method/probe.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1935,7 +1935,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
19351935
}
19361936
}
19371937

1938-
/// Determine if the associated item withe the given DefId matches
1938+
/// Determine if the associated item with the given DefId matches
19391939
/// the desired name via a doc alias.
19401940
fn matches_by_doc_alias(&self, def_id: DefId) -> bool {
19411941
let Some(name) = self.method_name else {

compiler/rustc_mir_build/src/build/scope.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
774774
let (current_root, parent_root) =
775775
if self.tcx.sess.opts.unstable_opts.maximal_hir_to_mir_coverage {
776776
// Some consumers of rustc need to map MIR locations back to HIR nodes. Currently
777-
// the the only part of rustc that tracks MIR -> HIR is the
777+
// the only part of rustc that tracks MIR -> HIR is the
778778
// `SourceScopeLocalData::lint_root` field that tracks lint levels for MIR
779779
// locations. Normally the number of source scopes is limited to the set of nodes
780780
// with lint annotations. The -Zmaximal-hir-to-mir-coverage flag changes this

compiler/rustc_mir_transform/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ fn run_analysis_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
507507
let passes: &[&dyn MirPass<'tcx>] = &[
508508
&cleanup_post_borrowck::CleanupPostBorrowck,
509509
&remove_noop_landing_pads::RemoveNoopLandingPads,
510-
&simplify::SimplifyCfg::EarlyOpt,
510+
&simplify::SimplifyCfg::PostAnalysis,
511511
&deref_separator::Derefer,
512512
];
513513

@@ -544,7 +544,7 @@ fn run_runtime_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
544544
let passes: &[&dyn MirPass<'tcx>] = &[
545545
&lower_intrinsics::LowerIntrinsics,
546546
&remove_place_mention::RemovePlaceMention,
547-
&simplify::SimplifyCfg::ElaborateDrops,
547+
&simplify::SimplifyCfg::PreOptimizations,
548548
];
549549

550550
pm::run_passes(tcx, body, passes, Some(MirPhase::Runtime(RuntimePhase::PostCleanup)));

compiler/rustc_mir_transform/src/simplify.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ pub enum SimplifyCfg {
3737
Initial,
3838
PromoteConsts,
3939
RemoveFalseEdges,
40-
EarlyOpt,
41-
ElaborateDrops,
40+
/// Runs at the beginning of "analysis to runtime" lowering, *before* drop elaboration.
41+
PostAnalysis,
42+
/// Runs at the end of "analysis to runtime" lowering, *after* drop elaboration.
43+
/// This is before the main optimization passes on runtime MIR kick in.
44+
PreOptimizations,
4245
Final,
4346
MakeShim,
4447
AfterUninhabitedEnumBranching,
@@ -50,8 +53,8 @@ impl SimplifyCfg {
5053
SimplifyCfg::Initial => "SimplifyCfg-initial",
5154
SimplifyCfg::PromoteConsts => "SimplifyCfg-promote-consts",
5255
SimplifyCfg::RemoveFalseEdges => "SimplifyCfg-remove-false-edges",
53-
SimplifyCfg::EarlyOpt => "SimplifyCfg-early-opt",
54-
SimplifyCfg::ElaborateDrops => "SimplifyCfg-elaborate-drops",
56+
SimplifyCfg::PostAnalysis => "SimplifyCfg-post-analysis",
57+
SimplifyCfg::PreOptimizations => "SimplifyCfg-pre-optimizations",
5558
SimplifyCfg::Final => "SimplifyCfg-final",
5659
SimplifyCfg::MakeShim => "SimplifyCfg-make_shim",
5760
SimplifyCfg::AfterUninhabitedEnumBranching => {

tests/mir-opt/array_index_is_temporary.main.SimplifyCfg-elaborate-drops.after.panic-abort.mir tests/mir-opt/array_index_is_temporary.main.SimplifyCfg-pre-optimizations.after.panic-abort.mir

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// MIR for `main` after SimplifyCfg-elaborate-drops
1+
// MIR for `main` after SimplifyCfg-pre-optimizations
22

33
fn main() -> () {
44
let mut _0: ();

tests/mir-opt/array_index_is_temporary.main.SimplifyCfg-elaborate-drops.after.panic-unwind.mir tests/mir-opt/array_index_is_temporary.main.SimplifyCfg-pre-optimizations.after.panic-unwind.mir

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// MIR for `main` after SimplifyCfg-elaborate-drops
1+
// MIR for `main` after SimplifyCfg-pre-optimizations
22

33
fn main() -> () {
44
let mut _0: ();

0 commit comments

Comments
 (0)