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
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 }
Comment on lines +4714 to +4715
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Field ordering is not consistent between new and struct definition. Unless there is a good reason for that, I think it would be less surprising if the order were consistent.

}
}

#[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),
));
Comment on lines +4748 to 4750
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The else-branch might be unreachable because of the values of variables in the program, which would mean the same kind of disclaimer would apply to this case as well.

}
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
Comment on lines +4808 to 4811
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about the case where the for-loop loops i times and let i = 1;? Are we then not also in the situation where the compiler does not consider a value for its diagnostic?

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
Comment on lines +4839 to 4842
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could not a pattern match lead to the same kind of diagnostic? If a variable is initialized as None, and I match on Some(_) and then don't initialize something in that branch, then we are in the same situation I think.

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