Skip to content

Commit 9b21131

Browse files
committed
Auto merge of rust-lang#98360 - estebank:uninit-binding, r=oli-obk
On partial uninit error point at where we need init When a binding is declared without a value, borrowck verifies that all codepaths have *one* assignment to them to initialize them fully. If there are any cases where a condition can be met that leaves the binding uninitialized or we attempt to initialize a field of an uninitialized binding, we emit E0381. We now look at all the statements that initialize the binding, and use them to explore branching code paths that *don't* and point at them. If we find *no* potential places where an assignment to the binding might be missing, we display the spans of all the existing initializers to provide some context. Fix rust-lang#97956.
2 parents 1517f5d + 2a2df9d commit 9b21131

File tree

111 files changed

+1060
-445
lines changed

Some content is hidden

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

111 files changed

+1060
-445
lines changed

compiler/rustc_borrowck/src/borrowck_errors.rs

+4-22
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,6 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
3333
err
3434
}
3535

36-
pub(crate) fn cannot_act_on_uninitialized_variable(
37-
&self,
38-
span: Span,
39-
verb: &str,
40-
desc: &str,
41-
) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
42-
struct_span_err!(
43-
self,
44-
span,
45-
E0381,
46-
"{} of possibly-uninitialized variable: `{}`",
47-
verb,
48-
desc,
49-
)
50-
}
51-
5236
pub(crate) fn cannot_mutably_borrow_multiply(
5337
&self,
5438
new_loan_span: Span,
@@ -175,8 +159,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
175159
self,
176160
new_loan_span,
177161
E0501,
178-
"cannot borrow {}{} as {} because previous closure \
179-
requires unique access",
162+
"cannot borrow {}{} as {} because previous closure requires unique access",
180163
desc_new,
181164
opt_via,
182165
kind_new,
@@ -453,9 +436,8 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
453436
self,
454437
closure_span,
455438
E0373,
456-
"{} may outlive the current function, \
457-
but it borrows {}, \
458-
which is owned by the current function",
439+
"{} may outlive the current function, but it borrows {}, which is owned by the current \
440+
function",
459441
closure_kind,
460442
borrowed_path,
461443
);
@@ -479,7 +461,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
479461
}
480462

481463
#[rustc_lint_diagnostics]
482-
fn struct_span_err_with_code<S: Into<MultiSpan>>(
464+
pub(crate) fn struct_span_err_with_code<S: Into<MultiSpan>>(
483465
&self,
484466
sp: S,
485467
msg: impl Into<DiagnosticMessage>,

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+275-20
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ use either::Either;
22
use rustc_const_eval::util::CallKind;
33
use rustc_data_structures::captures::Captures;
44
use rustc_data_structures::fx::FxHashSet;
5-
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
5+
use rustc_errors::{
6+
struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
7+
};
68
use rustc_hir as hir;
79
use rustc_hir::def_id::DefId;
10+
use rustc_hir::intravisit::{walk_expr, Visitor};
811
use rustc_hir::{AsyncGeneratorKind, GeneratorKind};
912
use rustc_infer::infer::TyCtxtInferExt;
1013
use rustc_infer::traits::ObligationCause;
@@ -18,6 +21,7 @@ use rustc_middle::ty::{
1821
self, subst::Subst, suggest_constraining_type_params, EarlyBinder, PredicateKind, Ty,
1922
};
2023
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
24+
use rustc_span::hygiene::DesugaringKind;
2125
use rustc_span::symbol::sym;
2226
use rustc_span::{BytePos, Span};
2327
use rustc_trait_selection::infer::InferCtxtExt;
@@ -94,32 +98,20 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
9498
return;
9599
}
96100

97-
let item_msg =
98-
match self.describe_place_with_options(used_place, IncludingDowncast(true)) {
99-
Some(name) => format!("`{}`", name),
100-
None => "value".to_owned(),
101-
};
102-
let mut err = self.cannot_act_on_uninitialized_variable(
101+
let err = self.report_use_of_uninitialized(
102+
mpi,
103+
used_place,
104+
moved_place,
105+
desired_action,
103106
span,
104-
desired_action.as_noun(),
105-
&self
106-
.describe_place_with_options(moved_place, IncludingDowncast(true))
107-
.unwrap_or_else(|| "_".to_owned()),
107+
use_spans,
108108
);
109-
err.span_label(span, format!("use of possibly-uninitialized {}", item_msg));
110-
111-
use_spans.var_span_label_path_only(
112-
&mut err,
113-
format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()),
114-
);
115-
116109
self.buffer_error(err);
117110
} else {
118111
if let Some((reported_place, _)) = self.has_move_error(&move_out_indices) {
119112
if self.prefixes(*reported_place, PrefixSet::All).any(|p| p == used_place) {
120113
debug!(
121-
"report_use_of_moved_or_uninitialized place: error suppressed \
122-
mois={:?}",
114+
"report_use_of_moved_or_uninitialized place: error suppressed mois={:?}",
123115
move_out_indices
124116
);
125117
return;
@@ -326,6 +318,130 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
326318
}
327319
}
328320

321+
fn report_use_of_uninitialized(
322+
&self,
323+
mpi: MovePathIndex,
324+
used_place: PlaceRef<'tcx>,
325+
moved_place: PlaceRef<'tcx>,
326+
desired_action: InitializationRequiringAction,
327+
span: Span,
328+
use_spans: UseSpans<'tcx>,
329+
) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
330+
// We need all statements in the body where the binding was assigned to to later find all
331+
// the branching code paths where the binding *wasn't* assigned to.
332+
let inits = &self.move_data.init_path_map[mpi];
333+
let move_path = &self.move_data.move_paths[mpi];
334+
let decl_span = self.body.local_decls[move_path.place.local].source_info.span;
335+
let mut spans = vec![];
336+
for init_idx in inits {
337+
let init = &self.move_data.inits[*init_idx];
338+
let span = init.span(&self.body);
339+
if !span.is_dummy() {
340+
spans.push(span);
341+
}
342+
}
343+
344+
let (name, desc) =
345+
match self.describe_place_with_options(moved_place, IncludingDowncast(true)) {
346+
Some(name) => (format!("`{name}`"), format!("`{name}` ")),
347+
None => ("the variable".to_string(), String::new()),
348+
};
349+
let path = match self.describe_place_with_options(used_place, IncludingDowncast(true)) {
350+
Some(name) => format!("`{name}`"),
351+
None => "value".to_string(),
352+
};
353+
354+
// We use the statements were the binding was initialized, and inspect the HIR to look
355+
// for the branching codepaths that aren't covered, to point at them.
356+
let hir_id = self.mir_hir_id();
357+
let map = self.infcx.tcx.hir();
358+
let body_id = map.body_owned_by(hir_id);
359+
let body = map.body(body_id);
360+
361+
let mut visitor = ConditionVisitor { spans: &spans, name: &name, errors: vec![] };
362+
visitor.visit_body(&body);
363+
364+
let isnt_initialized = if let InitializationRequiringAction::PartialAssignment
365+
| InitializationRequiringAction::Assignment = desired_action
366+
{
367+
// The same error is emitted for bindings that are *sometimes* initialized and the ones
368+
// that are *partially* initialized by assigning to a field of an uninitialized
369+
// binding. We differentiate between them for more accurate wording here.
370+
"isn't fully initialized"
371+
} else if spans
372+
.iter()
373+
.filter(|i| {
374+
// We filter these to avoid misleading wording in cases like the following,
375+
// where `x` has an `init`, but it is in the same place we're looking at:
376+
// ```
377+
// let x;
378+
// x += 1;
379+
// ```
380+
!i.contains(span)
381+
// We filter these to avoid incorrect main message on `match-cfg-fake-edges.rs`
382+
&& !visitor
383+
.errors
384+
.iter()
385+
.map(|(sp, _)| *sp)
386+
.any(|sp| span < sp && !sp.contains(span))
387+
})
388+
.count()
389+
== 0
390+
{
391+
"isn't initialized"
392+
} else {
393+
"is possibly-uninitialized"
394+
};
395+
396+
let used = desired_action.as_general_verb_in_past_tense();
397+
let mut err =
398+
struct_span_err!(self, span, E0381, "{used} binding {desc}{isnt_initialized}");
399+
use_spans.var_span_label_path_only(
400+
&mut err,
401+
format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()),
402+
);
403+
404+
if let InitializationRequiringAction::PartialAssignment
405+
| InitializationRequiringAction::Assignment = desired_action
406+
{
407+
err.help(
408+
"partial initialization isn't supported, fully initialize the binding with a \
409+
default value and mutate it, or use `std::mem::MaybeUninit`",
410+
);
411+
}
412+
err.span_label(span, format!("{path} {used} here but it {isnt_initialized}"));
413+
414+
let mut shown = false;
415+
for (sp, label) in visitor.errors {
416+
if sp < span && !sp.overlaps(span) {
417+
// When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention
418+
// match arms coming after the primary span because they aren't relevant:
419+
// ```
420+
// let x;
421+
// match y {
422+
// _ if { x = 2; true } => {}
423+
// _ if {
424+
// x; //~ ERROR
425+
// false
426+
// } => {}
427+
// _ => {} // We don't want to point to this.
428+
// };
429+
// ```
430+
err.span_label(sp, &label);
431+
shown = true;
432+
}
433+
}
434+
if !shown {
435+
for sp in &spans {
436+
if *sp < span && !sp.overlaps(span) {
437+
err.span_label(*sp, "binding initialized here in some conditions");
438+
}
439+
}
440+
}
441+
err.span_label(decl_span, "binding declared here but left uninitialized");
442+
err
443+
}
444+
329445
fn suggest_borrow_fn_like(
330446
&self,
331447
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
@@ -2448,3 +2564,142 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> {
24482564
}
24492565
}
24502566
}
2567+
2568+
/// Detect whether one of the provided spans is a statement nested within the top-most visited expr
2569+
struct ReferencedStatementsVisitor<'a>(&'a [Span], bool);
2570+
2571+
impl<'a, 'v> Visitor<'v> for ReferencedStatementsVisitor<'a> {
2572+
fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
2573+
match s.kind {
2574+
hir::StmtKind::Semi(expr) if self.0.contains(&expr.span) => {
2575+
self.1 = true;
2576+
}
2577+
_ => {}
2578+
}
2579+
}
2580+
}
2581+
2582+
/// Given a set of spans representing statements initializing the relevant binding, visit all the
2583+
/// function expressions looking for branching code paths that *do not* initialize the binding.
2584+
struct ConditionVisitor<'b> {
2585+
spans: &'b [Span],
2586+
name: &'b str,
2587+
errors: Vec<(Span, String)>,
2588+
}
2589+
2590+
impl<'b, 'v> Visitor<'v> for ConditionVisitor<'b> {
2591+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
2592+
match ex.kind {
2593+
hir::ExprKind::If(cond, body, None) => {
2594+
// `if` expressions with no `else` that initialize the binding might be missing an
2595+
// `else` arm.
2596+
let mut v = ReferencedStatementsVisitor(self.spans, false);
2597+
v.visit_expr(body);
2598+
if v.1 {
2599+
self.errors.push((
2600+
cond.span,
2601+
format!(
2602+
"if this `if` condition is `false`, {} is not initialized",
2603+
self.name,
2604+
),
2605+
));
2606+
self.errors.push((
2607+
ex.span.shrink_to_hi(),
2608+
format!("an `else` arm might be missing here, initializing {}", self.name),
2609+
));
2610+
}
2611+
}
2612+
hir::ExprKind::If(cond, body, Some(other)) => {
2613+
// `if` expressions where the binding is only initialized in one of the two arms
2614+
// might be missing a binding initialization.
2615+
let mut a = ReferencedStatementsVisitor(self.spans, false);
2616+
a.visit_expr(body);
2617+
let mut b = ReferencedStatementsVisitor(self.spans, false);
2618+
b.visit_expr(other);
2619+
match (a.1, b.1) {
2620+
(true, true) | (false, false) => {}
2621+
(true, false) => {
2622+
if other.span.is_desugaring(DesugaringKind::WhileLoop) {
2623+
self.errors.push((
2624+
cond.span,
2625+
format!(
2626+
"if this condition isn't met and the `while` loop runs 0 \
2627+
times, {} is not initialized",
2628+
self.name
2629+
),
2630+
));
2631+
} else {
2632+
self.errors.push((
2633+
body.span.shrink_to_hi().until(other.span),
2634+
format!(
2635+
"if the `if` condition is `false` and this `else` arm is \
2636+
executed, {} is not initialized",
2637+
self.name
2638+
),
2639+
));
2640+
}
2641+
}
2642+
(false, true) => {
2643+
self.errors.push((
2644+
cond.span,
2645+
format!(
2646+
"if this condition is `true`, {} is not initialized",
2647+
self.name
2648+
),
2649+
));
2650+
}
2651+
}
2652+
}
2653+
hir::ExprKind::Match(e, arms, loop_desugar) => {
2654+
// If the binding is initialized in one of the match arms, then the other match
2655+
// arms might be missing an initialization.
2656+
let results: Vec<bool> = arms
2657+
.iter()
2658+
.map(|arm| {
2659+
let mut v = ReferencedStatementsVisitor(self.spans, false);
2660+
v.visit_arm(arm);
2661+
v.1
2662+
})
2663+
.collect();
2664+
if results.iter().any(|x| *x) && !results.iter().all(|x| *x) {
2665+
for (arm, seen) in arms.iter().zip(results) {
2666+
if !seen {
2667+
if loop_desugar == hir::MatchSource::ForLoopDesugar {
2668+
self.errors.push((
2669+
e.span,
2670+
format!(
2671+
"if the `for` loop runs 0 times, {} is not initialized ",
2672+
self.name
2673+
),
2674+
));
2675+
} else if let Some(guard) = &arm.guard {
2676+
self.errors.push((
2677+
arm.pat.span.to(guard.body().span),
2678+
format!(
2679+
"if this pattern and condition are matched, {} is not \
2680+
initialized",
2681+
self.name
2682+
),
2683+
));
2684+
} else {
2685+
self.errors.push((
2686+
arm.pat.span,
2687+
format!(
2688+
"if this pattern is matched, {} is not initialized",
2689+
self.name
2690+
),
2691+
));
2692+
}
2693+
}
2694+
}
2695+
}
2696+
}
2697+
// FIXME: should we also account for binops, particularly `&&` and `||`? `try` should
2698+
// also be accounted for. For now it is fine, as if we don't find *any* relevant
2699+
// branching code paths, we point at the places where the binding *is* initialized for
2700+
// *some* context.
2701+
_ => {}
2702+
}
2703+
walk_expr(self, ex);
2704+
}
2705+
}

0 commit comments

Comments
 (0)