Skip to content
Merged
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
80 changes: 71 additions & 9 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};
use rustc_hir::{
CoroutineDesugaring, CoroutineKind, CoroutineSource, LangItem, PatField, find_attr,
};
use rustc_index::bit_set::DenseBitSet;
use rustc_middle::bug;
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::mir::{
Expand All @@ -28,7 +29,7 @@ use rustc_middle::ty::{
self, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,
suggest_constraining_type_params,
};
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
use rustc_mir_dataflow::move_paths::{Init, InitKind, InitLocation, MoveOutIndex, MovePathIndex};
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::hygiene::DesugaringKind;
use rustc_span::{BytePos, ExpnKind, Ident, MacroKind, Span, Symbol, kw, sym};
Expand Down Expand Up @@ -110,6 +111,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
used_place,
moved_place,
desired_action,
location,
span,
use_spans,
);
Expand Down Expand Up @@ -769,12 +771,61 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
}
}

/// Returns `true` if the given initialization can reach the error location.
///
/// This is used to determine whether an initialization should be considered
/// when reporting diagnostics at `err_location`.
///
/// The check proceeds in two stages:
///
/// 1. If the initialization originates from a function argument, it is
/// considered reachable by definition.
/// 2. If the initialization's basic block dominates the error block, then
/// every path to the error must pass through the initialization, so it is
/// reachable.
/// 3. Otherwise, perform a graph traversal over the MIR control-flow graph to
/// determine whether any path exists from the initialization block to the
/// error block.
///
/// The dominance check acts as a fast path for the common case, while the CFG
/// traversal handles cases where the initialization does not dominate the
/// error location but can still reach it through an alternate control-flow
/// path.
fn is_init_reachable(&self, init: &Init, err_location: mir::Location) -> bool {
Comment thread
estebank marked this conversation as resolved.
let dominators = self.body.basic_blocks.dominators();
let init_block = match init.location {
InitLocation::Argument(_) => return true,
InitLocation::Statement(location) => location.block,
};
let err_block = err_location.block;
if dominators.dominates(init_block, err_block) {
return true;
}
// If init_block doesn't dominate error_block, check if there is any valid path from the
// initialization block to the error block in the Control Flow Graph.
let mut visited = DenseBitSet::new_empty(self.body.basic_blocks.len());
let mut stack = vec![init_block];
while let Some(block) = stack.pop() {
if block == err_block {
return true;
}
if visited.insert(block) {
let data = &self.body.basic_blocks[block];
for successor in data.terminator().successors() {
stack.push(successor);
}
}
}
false
}

fn report_use_of_uninitialized(
&self,
mpi: MovePathIndex,
used_place: PlaceRef<'tcx>,
moved_place: PlaceRef<'tcx>,
desired_action: InitializationRequiringAction,
location: Location,
span: Span,
use_spans: UseSpans<'tcx>,
) -> Diag<'infcx> {
Expand All @@ -783,15 +834,20 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
let inits = &self.move_data.init_path_map[mpi];
let move_path = &self.move_data.move_paths[mpi];
let decl_span = self.body.local_decls[move_path.place.local].source_info.span;
let mut spans_set = FxIndexSet::default();
let mut all_init_spans_set = FxIndexSet::default();
let mut reachable_spans_set = FxIndexSet::default();
for init_idx in inits {
let init = &self.move_data.inits[*init_idx];
let span = init.span(self.body);
if !span.is_dummy() {
spans_set.insert(span);
all_init_spans_set.insert(span);
if self.is_init_reachable(init, location) {
reachable_spans_set.insert(span);
}
}
}
let spans: Vec<_> = spans_set.into_iter().collect();
let all_init_spans: Vec<_> = all_init_spans_set.into_iter().collect();
let reachable_spans: Vec<_> = reachable_spans_set.into_iter().collect();

let (name, desc) = match self.describe_place_with_options(
moved_place,
Expand All @@ -812,9 +868,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
// for the branching codepaths that aren't covered, to point at them.
let tcx = self.infcx.tcx;
let body = tcx.hir_body_owned_by(self.mir_def_id());
let mut visitor = ConditionVisitor { tcx, spans, name, errors: vec![] };
let mut visitor =
ConditionVisitor { tcx, spans: all_init_spans.clone(), name, errors: vec![] };
visitor.visit_body(&body);
let spans = visitor.spans;

let mut show_assign_sugg = false;
let isnt_initialized = if let InitializationRequiringAction::PartialAssignment
Expand All @@ -824,7 +880,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
// that are *partially* initialized by assigning to a field of an uninitialized
// binding. We differentiate between them for more accurate wording here.
"isn't fully initialized"
} else if !spans.iter().any(|i| {
} else if !reachable_spans.iter().any(|i| {
// We filter these to avoid misleading wording in cases like the following,
// where `x` has an `init`, but it is in the same place we're looking at:
// ```
Expand All @@ -840,7 +896,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
.any(|sp| span < sp && !sp.contains(span))
}) {
show_assign_sugg = true;
"isn't initialized"
if all_init_spans.iter().any(|init_span| !init_span.contains(span))
&& reachable_spans.is_empty()
{
"isn't initialized on any path leading to this point"
} else {
"isn't initialized"
}
} else {
"is possibly-uninitialized"
};
Expand Down Expand Up @@ -887,7 +949,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
}
}
if !shown {
for sp in &spans {
for sp in &reachable_spans {
if *sp < span && !sp.overlaps(span) {
err.span_label(*sp, "binding initialized here in some conditions");
}
Expand Down
12 changes: 12 additions & 0 deletions tests/ui/borrowck/borrowck-if-else-uninitialized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn foo(b: bool) {
let x;
if b {
x = 1;
} else {
println!("{x}"); //~ ERROR E0381
}
}

fn main() {
foo(true);
}
20 changes: 20 additions & 0 deletions tests/ui/borrowck/borrowck-if-else-uninitialized.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error[E0381]: used binding `x` isn't initialized on any path leading to this point
--> $DIR/borrowck-if-else-uninitialized.rs:6:20
|
LL | let x;
| - binding declared here but left uninitialized
...
LL | } else {
| ------ if the `if` condition is `false` and this `else` arm is executed, `x` is not initialized
LL | println!("{x}");
| ^ `x` used here but it isn't initialized on any path leading to this point
|
= note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown
help: consider assigning a value
|
LL | let x = 42;
| ++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0381`.
16 changes: 16 additions & 0 deletions tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fn main() {
let internal_code: u32;
let is_admin = true;
let access_level = 2;
if is_admin {
if access_level == 1 {
internal_code = 101;
} else if access_level == 2 {
println!("Admin access pending for code: {internal_code}"); //~ ERROR E0381
} else {
internal_code = 103;
}
} else {
internal_code = 404;
}
}
20 changes: 20 additions & 0 deletions tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error[E0381]: used binding `internal_code` isn't initialized on any path leading to this point
--> $DIR/borrowck-if-elseif-else-uninitialezed.rs:9:55
|
LL | let internal_code: u32;
| ------------- binding declared here but left uninitialized
...
LL | } else if access_level == 2 {
| ----------------- if this condition is `true`, `internal_code` is not initialized
LL | println!("Admin access pending for code: {internal_code}");
| ^^^^^^^^^^^^^ `internal_code` used here but it isn't initialized on any path leading to this point
|
= note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown
help: consider assigning a value
|
LL | let internal_code: u32 = 42;
| ++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0381`.
20 changes: 20 additions & 0 deletions tests/ui/borrowck/borrowck-match-multiple-uninitialized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
enum Status {
Active,
Inactive,
Pending,
}
fn main() {
let message: &str; // Declared but not initialized
let current_status = Status::Pending;
match current_status {
Status::Active => {
message = "System is live.";
}
Status::Inactive => {
message = "System is down.";
}
Status::Pending => {
println!("{message}"); //~ ERROR E0381
}
}
}
19 changes: 19 additions & 0 deletions tests/ui/borrowck/borrowck-match-multiple-uninitialized.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0381]: used binding `message` isn't initialized on any path leading to this point
--> $DIR/borrowck-match-multiple-uninitialized.rs:17:24
|
LL | let message: &str; // Declared but not initialized
| ------- binding declared here but left uninitialized
...
LL | Status::Pending => {
| --------------- if this pattern is matched, `message` is not initialized
LL | println!("{message}");
| ^^^^^^^ `message` used here but it isn't initialized on any path leading to this point
|
help: consider assigning a value
|
LL | let message: &str = ""; // Declared but not initialized
| ++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0381`.
8 changes: 8 additions & 0 deletions tests/ui/borrowck/borrowck-match-uninitialized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
let x;

match true {
true => x = 42,
false => println!("{x}") //~ ERROR E0381
}
}

@Unique-Usman Unique-Usman Jun 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be something like

fn main() {
    let x;

    match true {
        true => x = 42,
        false =>  println!("{x}"), //~ ERROR E0381
    }
}

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed.

17 changes: 17 additions & 0 deletions tests/ui/borrowck/borrowck-match-uninitialized.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0381]: used binding `x` isn't initialized on any path leading to this point
--> $DIR/borrowck-match-uninitialized.rs:6:29
|
LL | let x;
| - binding declared here but left uninitialized
...
LL | false => println!("{x}")
| ^ `x` used here but it isn't initialized on any path leading to this point
|
help: consider assigning a value
|
LL | let x = 42;
| ++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0381`.
Loading