diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index fcf4bf4a7b7a4..efca585a03dad 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -846,7 +846,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { && !visitor .errors .iter() - .map(|(sp, _)| *sp) + .map(|error| error.span) .any(|sp| span < sp && !sp.contains(span)) }) { show_assign_sugg = true; @@ -875,8 +875,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { err.span_label(span, format!("{path} {used} here but it {isnt_initialized}")); let mut shown = false; - for (sp, label) in visitor.errors { - if sp < span && !sp.overlaps(span) { + let mut shown_condition_value = false; + for error in visitor.errors { + if error.span < span && !error.span.overlaps(span) { // When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention // match arms coming after the primary span because they aren't relevant: // ``` @@ -890,7 +891,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // _ => {} // We don't want to point to this. // }; // ``` - err.span_label(sp, label); + shown_condition_value |= error.kind.describes_condition_value(); + err.span_label(error.span, error.label); shown = true; } } @@ -903,6 +905,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } err.span_label(decl_span, "binding declared here but left uninitialized"); + if shown_condition_value { + err.note( + "when checking initialization, the compiler describes possible control-flow paths \ + without evaluating whether branch conditions can actually have the values shown", + ); + } if show_assign_sugg { struct LetVisitor { decl_span: Span, @@ -4693,7 +4701,31 @@ struct ConditionVisitor<'tcx> { tcx: TyCtxt<'tcx>, spans: Vec, name: String, - errors: Vec<(Span, String)>, + errors: Vec, +} + +struct ConditionError { + span: Span, + label: String, + kind: ConditionErrorKind, +} + +impl ConditionError { + fn new(span: Span, kind: ConditionErrorKind, label: String) -> Self { + Self { span, label, kind } + } +} + +#[derive(Clone, Copy)] +enum ConditionErrorKind { + ConditionValue, + Other, +} + +impl ConditionErrorKind { + fn describes_condition_value(self) -> bool { + matches!(self, Self::ConditionValue) + } } impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { @@ -4703,15 +4735,17 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { // `if` expressions with no `else` that initialize the binding might be missing an // `else` arm. if ReferencedStatementsVisitor(&self.spans).visit_expr(body).is_break() { - self.errors.push(( + self.errors.push(ConditionError::new( cond.span, + ConditionErrorKind::ConditionValue, format!( "if this `if` condition is `false`, {} is not initialized", self.name, ), )); - self.errors.push(( + self.errors.push(ConditionError::new( ex.span.shrink_to_hi(), + ConditionErrorKind::Other, format!("an `else` arm might be missing here, initializing {}", self.name), )); } @@ -4725,8 +4759,9 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { (true, true) | (false, false) => {} (true, false) => { if other.span.is_desugaring(DesugaringKind::WhileLoop) { - self.errors.push(( + self.errors.push(ConditionError::new( cond.span, + ConditionErrorKind::ConditionValue, format!( "if this condition isn't met and the `while` loop runs 0 \ times, {} is not initialized", @@ -4734,8 +4769,9 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { ), )); } else { - self.errors.push(( + self.errors.push(ConditionError::new( body.span.shrink_to_hi().until(other.span), + ConditionErrorKind::ConditionValue, format!( "if the `if` condition is `false` and this `else` arm is \ executed, {} is not initialized", @@ -4745,8 +4781,9 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { } } (false, true) => { - self.errors.push(( + self.errors.push(ConditionError::new( cond.span, + ConditionErrorKind::ConditionValue, format!( "if this condition is `true`, {} is not initialized", self.name @@ -4766,8 +4803,9 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { for (arm, seen) in arms.iter().zip(results) { if !seen { if loop_desugar == hir::MatchSource::ForLoopDesugar { - self.errors.push(( + self.errors.push(ConditionError::new( e.span, + ConditionErrorKind::Other, format!( "if the `for` loop runs 0 times, {} is not initialized", self.name @@ -4780,8 +4818,9 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { ) { continue; } - self.errors.push(( + self.errors.push(ConditionError::new( arm.pat.span.to(guard.span), + ConditionErrorKind::ConditionValue, format!( "if this pattern and condition are matched, {} is not \ initialized", @@ -4795,8 +4834,9 @@ impl<'v, 'tcx> Visitor<'v> for ConditionVisitor<'tcx> { ) { continue; } - self.errors.push(( + self.errors.push(ConditionError::new( arm.pat.span, + ConditionErrorKind::Other, format!( "if this pattern is matched, {} is not initialized", self.name diff --git a/tests/ui/async-await/no-non-guaranteed-initialization.stderr b/tests/ui/async-await/no-non-guaranteed-initialization.stderr index 7adccdba2f87c..4e62a307e4b0b 100644 --- a/tests/ui/async-await/no-non-guaranteed-initialization.stderr +++ b/tests/ui/async-await/no-non-guaranteed-initialization.stderr @@ -10,6 +10,8 @@ LL | } | - an `else` arm might be missing here, initializing `y` LL | y | ^ `y` used here but it is possibly-uninitialized + | + = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck-if-no-else.stderr b/tests/ui/borrowck/borrowck-if-no-else.stderr index f1fad2d36dc5e..48a526b744b03 100644 --- a/tests/ui/borrowck/borrowck-if-no-else.stderr +++ b/tests/ui/borrowck/borrowck-if-no-else.stderr @@ -8,6 +8,8 @@ LL | let x: isize; if 1 > 2 { x = 10; } | binding declared here but left uninitialized LL | foo(x); | ^ `x` used here but it is possibly-uninitialized + | + = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck-if-with-else.stderr b/tests/ui/borrowck/borrowck-if-with-else.stderr index c246e41487fc3..be3b33a09751c 100644 --- a/tests/ui/borrowck/borrowck-if-with-else.stderr +++ b/tests/ui/borrowck/borrowck-if-with-else.stderr @@ -8,6 +8,8 @@ LL | if 1 > 2 { ... LL | foo(x); | ^ `x` used here but it is possibly-uninitialized + | + = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck-while-break.stderr b/tests/ui/borrowck/borrowck-while-break.stderr index 68333ce0a75d3..c4af9276b6009 100644 --- a/tests/ui/borrowck/borrowck-while-break.stderr +++ b/tests/ui/borrowck/borrowck-while-break.stderr @@ -8,6 +8,8 @@ LL | while cond { ... LL | println!("{}", v); | ^ `v` used here but it is possibly-uninitialized + | + = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck-while.stderr b/tests/ui/borrowck/borrowck-while.stderr index d560b9c02fb68..ba703dd62c103 100644 --- a/tests/ui/borrowck/borrowck-while.stderr +++ b/tests/ui/borrowck/borrowck-while.stderr @@ -7,6 +7,8 @@ LL | while 1 == 1 { x = 10; } | ------ if this condition isn't met and the `while` loop runs 0 times, `x` is not initialized LL | return x; | ^ `x` used here but it is possibly-uninitialized + | + = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/branch-condition-flow-issue-156494.rs b/tests/ui/borrowck/branch-condition-flow-issue-156494.rs new file mode 100644 index 0000000000000..d2f0404ab9ce6 --- /dev/null +++ b/tests/ui/borrowck/branch-condition-flow-issue-156494.rs @@ -0,0 +1,55 @@ +//@ edition: 2024 + +fn main() { + let v1: Vec = vec![1, 2, 3]; + let mut iter = v1.iter(); + + let mut first_encounter = true; + let mut token: i32; + let mut following_token: i32; + loop { + if first_encounter { + if let Some(token) = iter.next() { + match iter.next() { + Some(temp) => { + following_token = *temp; + first_encounter = false; + println!("{} followed by {}", token, following_token); + } + _ => { + println!("{} is last", token); + } + } + } else { + break; + } + } else { + token = following_token; //~ ERROR used binding `following_token` isn't initialized + first_encounter = true; + match iter.next() { + Some(temp) => { + following_token = *temp; + first_encounter = false; + println!("{} followed by {}", token, following_token); + } + _ => { + println!("{} is last", token); + } + } + } + } +} + +fn guarded_match(value: i32, condition: bool) { + let y; + match value { + 0 => { + y = 1; + } + _ if condition => {} + _ => { + y = 2; + } + } + let _z = y; //~ ERROR used binding `y` is possibly-uninitialized +} diff --git a/tests/ui/borrowck/branch-condition-flow-issue-156494.stderr b/tests/ui/borrowck/branch-condition-flow-issue-156494.stderr new file mode 100644 index 0000000000000..04fb77a72a6f7 --- /dev/null +++ b/tests/ui/borrowck/branch-condition-flow-issue-156494.stderr @@ -0,0 +1,38 @@ +error[E0381]: used binding `following_token` isn't initialized + --> $DIR/branch-condition-flow-issue-156494.rs:27:21 + | +LL | let mut following_token: i32; + | ------------------- binding declared here but left uninitialized +... +LL | _ => { + | - if this pattern is matched, `following_token` is not initialized +... +LL | } else { + | ------ if the `if` condition is `false` and this `else` arm is executed, `following_token` is not initialized +... +LL | token = following_token; + | ^^^^^^^^^^^^^^^ `following_token` used here but it isn't initialized + | + = 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 mut following_token: i32 = 42; + | ++++ + +error[E0381]: used binding `y` is possibly-uninitialized + --> $DIR/branch-condition-flow-issue-156494.rs:54:14 + | +LL | let y; + | - binding declared here but left uninitialized +... +LL | _ if condition => {} + | -------------- if this pattern and condition are matched, `y` is not initialized +... +LL | let _z = y; + | ^ `y` used here but it is possibly-uninitialized + | + = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0381`.