-
-
Notifications
You must be signed in to change notification settings - Fork 15.1k
Clarify E0381 diagnostics for branch conditions #156603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<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> { | ||
|
|
@@ -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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| } | ||
|
|
@@ -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", | ||
|
|
@@ -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 | ||
|
Comment on lines
+4808
to
4811
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about the case where the for-loop loops |
||
|
|
@@ -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!( | ||
|
eval-exec marked this conversation as resolved.
|
||
| "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 | ||
|
Comment on lines
+4839
to
4842
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| 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 | ||
| } |
| 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`. |
There was a problem hiding this comment.
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
newand struct definition. Unless there is a good reason for that, I think it would be less surprising if the order were consistent.