Skip to content
Open
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
66 changes: 53 additions & 13 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
// ```
Expand All @@ -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;
}
}
Expand All @@ -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,
Expand Down Expand Up @@ -4693,7 +4701,31 @@ struct ConditionVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
spans: Vec<Span>,
name: String,
errors: Vec<(Span, String)>,
errors: Vec<ConditionError>,
}

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> {
Expand All @@ -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),
));
}
Expand All @@ -4725,17 +4759,19 @@ 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",
self.name
),
));
} 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",
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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!(
Comment thread
eval-exec marked this conversation as resolved.
"if this pattern and condition are matched, {} is not \
initialized",
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/ui/async-await/no-non-guaranteed-initialization.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions tests/ui/borrowck/borrowck-if-no-else.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions tests/ui/borrowck/borrowck-if-with-else.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions tests/ui/borrowck/borrowck-while-break.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions tests/ui/borrowck/borrowck-while.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
55 changes: 55 additions & 0 deletions tests/ui/borrowck/branch-condition-flow-issue-156494.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//@ edition: 2024

fn main() {
let v1: Vec<i32> = 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
}
38 changes: 38 additions & 0 deletions tests/ui/borrowck/branch-condition-flow-issue-156494.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading