From 417015defae158852a3f34c825741f494fad09e4 Mon Sep 17 00:00:00 2001 From: mazam-97 Date: Fri, 5 Jun 2026 06:25:19 +0530 Subject: [PATCH] fixed: avoid reporting unreachable initializations for uninitialized uses Mentored-by: Usman Akinyemi Signed-off-by: Dilshad Azam --- .../src/diagnostics/conflict_errors.rs | 80 ++++++++++++++++--- .../borrowck-if-else-uninitialized.rs | 12 +++ .../borrowck-if-else-uninitialized.stderr | 20 +++++ .../borrowck-if-elseif-else-uninitialezed.rs | 16 ++++ ...rrowck-if-elseif-else-uninitialezed.stderr | 20 +++++ .../borrowck-match-multiple-uninitialized.rs | 20 +++++ ...rrowck-match-multiple-uninitialized.stderr | 19 +++++ .../borrowck/borrowck-match-uninitialized.rs | 8 ++ .../borrowck-match-uninitialized.stderr | 17 ++++ 9 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 tests/ui/borrowck/borrowck-if-else-uninitialized.rs create mode 100644 tests/ui/borrowck/borrowck-if-else-uninitialized.stderr create mode 100644 tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.rs create mode 100644 tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.stderr create mode 100644 tests/ui/borrowck/borrowck-match-multiple-uninitialized.rs create mode 100644 tests/ui/borrowck/borrowck-match-multiple-uninitialized.stderr create mode 100644 tests/ui/borrowck/borrowck-match-uninitialized.rs create mode 100644 tests/ui/borrowck/borrowck-match-uninitialized.stderr diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index f1a22fd6af982..caa566e79e29b 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -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::{ @@ -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}; @@ -110,6 +111,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { used_place, moved_place, desired_action, + location, span, use_spans, ); @@ -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 { + 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> { @@ -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, @@ -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 @@ -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: // ``` @@ -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" }; @@ -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"); } diff --git a/tests/ui/borrowck/borrowck-if-else-uninitialized.rs b/tests/ui/borrowck/borrowck-if-else-uninitialized.rs new file mode 100644 index 0000000000000..8ca1fc33ad948 --- /dev/null +++ b/tests/ui/borrowck/borrowck-if-else-uninitialized.rs @@ -0,0 +1,12 @@ +fn foo(b: bool) { + let x; + if b { + x = 1; + } else { + println!("{x}"); //~ ERROR E0381 + } +} + +fn main() { + foo(true); +} diff --git a/tests/ui/borrowck/borrowck-if-else-uninitialized.stderr b/tests/ui/borrowck/borrowck-if-else-uninitialized.stderr new file mode 100644 index 0000000000000..543a3cfbe6807 --- /dev/null +++ b/tests/ui/borrowck/borrowck-if-else-uninitialized.stderr @@ -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`. diff --git a/tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.rs b/tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.rs new file mode 100644 index 0000000000000..034e6495f5eed --- /dev/null +++ b/tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.rs @@ -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; + } +} diff --git a/tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.stderr b/tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.stderr new file mode 100644 index 0000000000000..947809d51e967 --- /dev/null +++ b/tests/ui/borrowck/borrowck-if-elseif-else-uninitialezed.stderr @@ -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`. diff --git a/tests/ui/borrowck/borrowck-match-multiple-uninitialized.rs b/tests/ui/borrowck/borrowck-match-multiple-uninitialized.rs new file mode 100644 index 0000000000000..4db2ea4a5e0fc --- /dev/null +++ b/tests/ui/borrowck/borrowck-match-multiple-uninitialized.rs @@ -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 + } + } +} diff --git a/tests/ui/borrowck/borrowck-match-multiple-uninitialized.stderr b/tests/ui/borrowck/borrowck-match-multiple-uninitialized.stderr new file mode 100644 index 0000000000000..21b02cdf55d8b --- /dev/null +++ b/tests/ui/borrowck/borrowck-match-multiple-uninitialized.stderr @@ -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`. diff --git a/tests/ui/borrowck/borrowck-match-uninitialized.rs b/tests/ui/borrowck/borrowck-match-uninitialized.rs new file mode 100644 index 0000000000000..e81f9b03df62c --- /dev/null +++ b/tests/ui/borrowck/borrowck-match-uninitialized.rs @@ -0,0 +1,8 @@ +fn main() { + let x; + + match true { + true => x = 42, + false => println!("{x}") //~ ERROR E0381 + } +} diff --git a/tests/ui/borrowck/borrowck-match-uninitialized.stderr b/tests/ui/borrowck/borrowck-match-uninitialized.stderr new file mode 100644 index 0000000000000..5d15de3b2b52a --- /dev/null +++ b/tests/ui/borrowck/borrowck-match-uninitialized.stderr @@ -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`.