Skip to content

Commit 29e2aa1

Browse files
committed
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 unitialized 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.
1 parent 3e51277 commit 29e2aa1

File tree

106 files changed

+935
-441
lines changed

Some content is hidden

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

106 files changed

+935
-441
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

+200-22
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;
@@ -94,32 +97,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
9497
return;
9598
}
9699

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(
103-
span,
104-
desired_action.as_noun(),
105-
&self
106-
.describe_place_with_options(moved_place, IncludingDowncast(true))
107-
.unwrap_or_else(|| "_".to_owned()),
108-
);
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-
100+
let err =
101+
self.report_use_of_uninitialized(mpi, used_place, desired_action, span, use_spans);
116102
self.buffer_error(err);
117103
} else {
118104
if let Some((reported_place, _)) = self.has_move_error(&move_out_indices) {
119105
if self.prefixes(*reported_place, PrefixSet::All).any(|p| p == used_place) {
120106
debug!(
121-
"report_use_of_moved_or_uninitialized place: error suppressed \
122-
mois={:?}",
107+
"report_use_of_moved_or_uninitialized place: error suppressed mois={:?}",
123108
move_out_indices
124109
);
125110
return;
@@ -326,6 +311,99 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
326311
}
327312
}
328313

314+
fn report_use_of_uninitialized(
315+
&self,
316+
mpi: MovePathIndex,
317+
used_place: PlaceRef<'tcx>,
318+
desired_action: InitializationRequiringAction,
319+
span: Span,
320+
use_spans: UseSpans<'tcx>,
321+
) -> DiagnosticBuilder<'cx, ErrorGuaranteed> {
322+
// We need all statements in the body where the binding was assigned to to later find all
323+
// the branching code paths where the binding *wasn't* assigned to.
324+
let inits = &self.move_data.init_path_map[mpi];
325+
let move_path = &self.move_data.move_paths[mpi];
326+
let decl_span = self.body.local_decls[move_path.place.local].source_info.span;
327+
let mut spans = vec![];
328+
for init_idx in inits {
329+
let init = &self.move_data.inits[*init_idx];
330+
let span = init.span(&self.body);
331+
spans.push(span);
332+
}
333+
334+
let (item_msg, name, desc) =
335+
match self.describe_place_with_options(used_place, IncludingDowncast(true)) {
336+
Some(name) => (format!("`{name}`"), format!("`{name}`"), format!("`{name}` ")),
337+
None => ("value".to_string(), "the variable".to_string(), String::new()),
338+
};
339+
let initialized = if let InitializationRequiringAction::PartialAssignment = desired_action {
340+
// The same error is emitted for bindings that are *sometimes* initialized and the ones
341+
// that are *partially* initialized by assigning to a field of an uninitialized
342+
// binding. We differentiate between them for more accurate wording here.
343+
"fully initialized"
344+
} else if spans.iter().filter(|i| !i.contains(span)).count() == 0 {
345+
// We filter above to avoid misleading wording in cases like:
346+
// ```
347+
// let x;
348+
// x += 1;
349+
// ```
350+
"initialized"
351+
} else {
352+
"initialized in all conditions"
353+
};
354+
let mut err = struct_span_err!(self, span, E0381, "binding {desc}isn't {initialized}");
355+
use_spans.var_span_label_path_only(
356+
&mut err,
357+
format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()),
358+
);
359+
360+
if let InitializationRequiringAction::PartialAssignment = desired_action {
361+
err.help(
362+
"partial initialization isn't supported, fully initialize the binding with \
363+
a default value and mutate, or use `std::mem::MaybeUninit`",
364+
);
365+
}
366+
let verb = desired_action.as_verb_in_past_tense();
367+
err.span_label(span, format!("{item_msg} {verb} here but it isn't {initialized}",));
368+
369+
// We use the statements were the binding was initialized, and inspect the HIR to look
370+
// for the branching codepaths that aren't covered, to point at them.
371+
let hir_id = self.mir_hir_id();
372+
let map = self.infcx.tcx.hir();
373+
let body_id = map.body_owned_by(hir_id);
374+
let body = map.body(body_id);
375+
376+
let mut visitor = ConditionVisitor { spans: &spans, name: &name, errors: vec![] };
377+
visitor.visit_body(&body);
378+
if visitor.errors.is_empty() {
379+
for sp in &spans {
380+
if *sp < span && !sp.overlaps(span) {
381+
err.span_label(*sp, "binding initialized here in some conditions");
382+
}
383+
}
384+
}
385+
for (sp, label) in visitor.errors {
386+
if sp < span && !sp.overlaps(span) {
387+
// When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention
388+
// match arms coming after the primary span because they aren't relevant:
389+
// ```
390+
// let x;
391+
// match y {
392+
// _ if { x = 2; true } => {}
393+
// _ if {
394+
// x; //~ ERROR
395+
// false
396+
// } => {}
397+
// _ => {} // We don't want to point to this.
398+
// };
399+
// ```
400+
err.span_label(sp, &label);
401+
}
402+
}
403+
err.span_label(decl_span, "variable declared here");
404+
err
405+
}
406+
329407
fn suggest_borrow_fn_like(
330408
&self,
331409
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
@@ -2448,3 +2526,103 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> {
24482526
}
24492527
}
24502528
}
2529+
2530+
/// Detect whether one of the provided spans is a statement nested within the top-most visited expr
2531+
struct ReferencedStatementsVisitor<'a>(&'a [Span], bool);
2532+
2533+
impl<'a, 'v> Visitor<'v> for ReferencedStatementsVisitor<'a> {
2534+
fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
2535+
match s.kind {
2536+
hir::StmtKind::Semi(expr) if self.0.contains(&expr.span) => {
2537+
self.1 = true;
2538+
}
2539+
_ => {}
2540+
}
2541+
}
2542+
}
2543+
2544+
/// Given a set of spans representing statements initializing the relevant binding, visit all the
2545+
/// function expressions looking for branching code paths that *do not* initialize the binding.
2546+
struct ConditionVisitor<'b> {
2547+
spans: &'b [Span],
2548+
name: &'b str,
2549+
errors: Vec<(Span, String)>,
2550+
}
2551+
2552+
impl<'b, 'v> Visitor<'v> for ConditionVisitor<'b> {
2553+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
2554+
match ex.kind {
2555+
hir::ExprKind::If(cond, body, None) => {
2556+
// `if` expressions with no `else` that initialize the binding might be missing an
2557+
// `else` arm.
2558+
let mut v = ReferencedStatementsVisitor(self.spans, false);
2559+
v.visit_expr(body);
2560+
if v.1 {
2561+
self.errors.push((
2562+
cond.span,
2563+
format!(
2564+
"this `if` expression might be missing an `else` arm where {} is \
2565+
initialized",
2566+
self.name,
2567+
),
2568+
));
2569+
}
2570+
}
2571+
hir::ExprKind::If(cond, body, Some(other)) => {
2572+
// `if` expressions where the binding is only initialized in one of the two arms
2573+
// might be missing a binding initialization.
2574+
let mut a = ReferencedStatementsVisitor(self.spans, false);
2575+
a.visit_expr(body);
2576+
let mut b = ReferencedStatementsVisitor(self.spans, false);
2577+
b.visit_expr(other);
2578+
match (a.1, b.1) {
2579+
(true, true) | (false, false) => {}
2580+
(true, false) => {
2581+
self.errors.push((
2582+
cond.span,
2583+
format!("{} is uninitialized if this condition isn't met", self.name,),
2584+
));
2585+
}
2586+
(false, true) => {
2587+
self.errors.push((
2588+
cond.span,
2589+
format!("{} is uninitialized if this condition is met", self.name),
2590+
));
2591+
}
2592+
}
2593+
}
2594+
hir::ExprKind::Match(_, arms, _) => {
2595+
// If the binding is initialized in one of the match arms, then the other match
2596+
// arms might be missing an initialization.
2597+
let results: Vec<bool> = arms
2598+
.iter()
2599+
.map(|arm| {
2600+
let mut v = ReferencedStatementsVisitor(self.spans, false);
2601+
v.visit_arm(arm);
2602+
v.1
2603+
})
2604+
.collect();
2605+
if results.iter().any(|x| *x) && !results.iter().all(|x| *x) {
2606+
for (arm, seen) in arms.iter().zip(results) {
2607+
if !seen {
2608+
self.errors.push((
2609+
arm.pat.span,
2610+
format!(
2611+
"{} is uninitialized if this pattern is matched",
2612+
self.name
2613+
),
2614+
));
2615+
}
2616+
}
2617+
}
2618+
}
2619+
// FIXME: should we also account for binops, particularly `&&` and `||`? `try` should
2620+
// also be accounted for. For now it is fine, as if we don't find *any* relevant
2621+
// branching code paths, we point at the places where the binding *is* initialized for
2622+
// *some* context. We should also specialize the output for `while` and `for` loops,
2623+
// but for now we can rely on their desugaring to provide appropriate output.
2624+
_ => {}
2625+
}
2626+
walk_expr(self, ex);
2627+
}
2628+
}

src/test/ui/asm/x86_64/type-check-5.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ fn main() {
1313

1414
let x: u64;
1515
asm!("{}", in(reg) x);
16-
//~^ ERROR use of possibly-uninitialized variable: `x`
16+
//~^ ERROR E0381
1717
let mut y: u64;
1818
asm!("{}", inout(reg) y);
19-
//~^ ERROR use of possibly-uninitialized variable: `y`
19+
//~^ ERROR E0381
2020
let _ = y;
2121

2222
// Outputs require mutable places

src/test/ui/asm/x86_64/type-check-5.stderr

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
error[E0381]: use of possibly-uninitialized variable: `x`
1+
error[E0381]: binding `x` isn't initialized
22
--> $DIR/type-check-5.rs:15:28
33
|
4+
LL | let x: u64;
5+
| - variable declared here
46
LL | asm!("{}", in(reg) x);
5-
| ^ use of possibly-uninitialized `x`
7+
| ^ `x` used here but it isn't initialized
68

7-
error[E0381]: use of possibly-uninitialized variable: `y`
9+
error[E0381]: binding `y` isn't initialized
810
--> $DIR/type-check-5.rs:18:9
911
|
12+
LL | let mut y: u64;
13+
| ----- variable declared here
1014
LL | asm!("{}", inout(reg) y);
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^ use of possibly-uninitialized `y`
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^ `y` used here but it isn't initialized
1216

1317
error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable
1418
--> $DIR/type-check-5.rs:26:29

src/test/ui/async-await/no-non-guaranteed-initialization.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ async fn no_non_guaranteed_initialization(x: usize) -> usize {
66
if x > 5 {
77
y = echo(10).await;
88
}
9-
y
10-
//~^ use of possibly-uninitialized variable: `y`
9+
y //~ ERROR E0381
1110
}
1211

1312
async fn echo(x: usize) -> usize { x + 1 }

src/test/ui/async-await/no-non-guaranteed-initialization.stderr

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
error[E0381]: use of possibly-uninitialized variable: `y`
1+
error[E0381]: binding `y` isn't initialized in all conditions
22
--> $DIR/no-non-guaranteed-initialization.rs:9:5
33
|
4+
LL | let y;
5+
| - variable declared here
6+
LL | if x > 5 {
7+
| ----- this `if` expression might be missing an `else` arm where `y` is initialized
8+
...
49
LL | y
5-
| ^ use of possibly-uninitialized `y`
10+
| ^ `y` used here but it isn't initialized in all conditions
611

712
error: aborting due to previous error
813

src/test/ui/async-await/partial-initialization-across-await.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,23 @@ async fn noop() {}
1010

1111
async fn test_tuple() {
1212
let mut t: (i32, i32);
13-
t.0 = 42;
14-
//~^ ERROR assign to part of possibly-uninitialized variable: `t` [E0381]
13+
t.0 = 42; //~ ERROR E0381
1514
noop().await;
1615
t.1 = 88;
1716
let _ = t;
1817
}
1918

2019
async fn test_tuple_struct() {
2120
let mut t: T;
22-
t.0 = 42;
23-
//~^ ERROR assign to part of possibly-uninitialized variable: `t` [E0381]
21+
t.0 = 42; //~ ERROR E0381
2422
noop().await;
2523
t.1 = 88;
2624
let _ = t;
2725
}
2826

2927
async fn test_struct() {
3028
let mut t: S;
31-
t.x = 42;
32-
//~^ ERROR assign to part of possibly-uninitialized variable: `t` [E0381]
29+
t.x = 42; //~ ERROR E0381
3330
noop().await;
3431
t.y = 88;
3532
let _ = t;

0 commit comments

Comments
 (0)