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
29 changes: 29 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use rustc_middle::ty::print::Print;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult, MoveOutIndex};
use rustc_session::lint::builtin::MACRO_EXTENDED_TEMPORARY_SCOPES;
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
Expand Down Expand Up @@ -1580,4 +1581,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
pub(crate) fn local_excluded_from_unused_mut_lint(&self, index: Local) -> bool {
self.local_name(index).is_none_or(|name| name.as_str().starts_with('_'))
}

/// Report a temporary whose scope will shorten in Rust 1.92 due to #145838.
pub(crate) fn lint_macro_extended_temporary_scope(
&self,
future_drop: Location,
borrow: &BorrowData<'tcx>,
) {
let tcx = self.infcx.tcx;
let temp_decl = &self.body.local_decls[borrow.borrowed_place.local];
let temp_span = temp_decl.source_info.span;
let lint_root = self.body.source_scopes[temp_decl.source_info.scope]
.local_data
.as_ref()
.unwrap_crate_local()
.lint_root;

let mut labels = MultiSpan::from_span(temp_span);
labels.push_span_label(temp_span, "this expression creates a temporary value...");
labels.push_span_label(
self.body.source_info(future_drop).span,
"...which will be dropped at the end of this block in Rust 1.92",
);

tcx.node_span_lint(MACRO_EXTENDED_TEMPORARY_SCOPES, lint_root, labels, |diag| {
diag.primary_message("temporary lifetime shortening in Rust 1.92");
Copy link
Contributor

Choose a reason for hiding this comment

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

This message seems like it'd be difficult to understand without context — to me, it almost seems like it's been cut off partway through. It might be better to be explicit here:

Suggested change
diag.primary_message("temporary lifetime shortening in Rust 1.92");
diag.primary_message("this temporary's lifetime will be shortened starting in Rust 1.92");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a bit difficult to understand as-is.. though I wonder if there's a way to write it without "this" in the primary message. My original take was "this temporary value's lifetime will shorten in Rust 1.92", but I moved away from it because something didn't feel right to me about the "this" and it felt redundant with the label. Maybe it would be fine?

diag.note("consider using a `let` binding to create a longer lived value");
Copy link
Contributor Author

@dianne dianne Sep 26, 2025

Choose a reason for hiding this comment

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

I've opted to use the "consider using a let binding" note from real borrowck diagnostics for simplicity, rather than providing a machine-applicable suggestion. To illustrate, consider real Rust 1.92 nightly diagnostics for the future breakage here:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:18:13
   |
18 |     pin!({ &temp() });
   |     --------^^^^^----
   |     |       |    |
   |     |       |    temporary value is freed at the end of this statement
   |     |       creates a temporary value which is freed while still in use
   |     borrow later stored here
   |
   = note: consider using a `let` binding to create a longer lived value

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:19:17
   |
19 |     pin!({ &mut [()] });
   |            -----^^^-
   |            |    |  |
   |            |    |  temporary value is freed at the end of this statement
   |            |    creates a temporary value which is freed while still in use
   |            borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

Note the differing spans. In many instances, the conflicting use reported in diagnostics is an implicit reborrow rather than the actual use in the macro expansion. I think getting more helpful diagnostics would either require:

  • Improving the actual borrowck diagnostic, then using that machinery.
  • Forcing maximal MIR-to-HIR mapping via source scopes when forward-incompatible drops are detected, then using bespoke diagnostic information stored earlier in compilation (e.g. the HIR source of forward-incompatible scope extension).

Another awkward aspect of that span imprecision is (without doing either of those things) it's hard to tell when adding a let statement actually can help. In non-pathological cases it should always either help or come with another note that the diagnostic originates in an external macro, so for simplicity, I've opted to always include it.

});
}
}
43 changes: 26 additions & 17 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,8 +843,8 @@ impl<'a, 'tcx> ResultsVisitor<'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<'a,
| StatementKind::ConstEvalCounter
| StatementKind::StorageLive(..) => {}
// This does not affect borrowck
StatementKind::BackwardIncompatibleDropHint { place, reason: BackwardIncompatibleDropReason::Edition2024 } => {
self.check_backward_incompatible_drop(location, **place, state);
StatementKind::BackwardIncompatibleDropHint { place, reason } => {
self.check_backward_incompatible_drop(location, **place, state, *reason);
}
StatementKind::StorageDead(local) => {
self.access_place(
Expand Down Expand Up @@ -1386,6 +1386,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
location: Location,
place: Place<'tcx>,
state: &BorrowckDomain,
reason: BackwardIncompatibleDropReason,
) {
let tcx = self.infcx.tcx;
// If this type does not need `Drop`, then treat it like a `StorageDead`.
Expand All @@ -1412,21 +1413,29 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
if matches!(borrow.kind, BorrowKind::Fake(_)) {
return ControlFlow::Continue(());
}
let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span();
let explain = this.explain_why_borrow_contains_point(
location,
borrow,
Some((WriteKind::StorageDeadOrDrop, place)),
);
this.infcx.tcx.node_span_lint(
TAIL_EXPR_DROP_ORDER,
CRATE_HIR_ID,
borrowed,
|diag| {
session_diagnostics::TailExprDropOrder { borrowed }.decorate_lint(diag);
explain.add_explanation_to_diagnostic(&this, diag, "", None, None);
},
);
match reason {
BackwardIncompatibleDropReason::Edition2024 => {
let borrowed = this.retrieve_borrow_spans(borrow).var_or_use_path_span();
let explain = this.explain_why_borrow_contains_point(
location,
borrow,
Some((WriteKind::StorageDeadOrDrop, place)),
);
this.infcx.tcx.node_span_lint(
TAIL_EXPR_DROP_ORDER,
CRATE_HIR_ID,
borrowed,
|diag| {
session_diagnostics::TailExprDropOrder { borrowed }
.decorate_lint(diag);
explain.add_explanation_to_diagnostic(&this, diag, "", None, None);
},
);
}
BackwardIncompatibleDropReason::MacroExtendedScope => {
this.lint_macro_extended_temporary_scope(location, borrow);
}
}
// We may stop at the first case
ControlFlow::Break(())
},
Expand Down
Loading
Loading