fix ICE when passing empty block to while-loop condition

This commit is contained in:
Michael Goulet 2022-02-21 19:49:15 -08:00
parent d973b358c6
commit 025b7c433c
4 changed files with 110 additions and 70 deletions

View file

@ -842,7 +842,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
); );
err.span_label(lhs.span, "cannot assign to this expression"); err.span_label(lhs.span, "cannot assign to this expression");
let mut parent = self.tcx.hir().get_parent_node(lhs.hir_id); self.comes_from_while_condition(lhs.hir_id, |expr| {
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"you might have meant to use pattern destructuring",
"let ".to_string(),
Applicability::MachineApplicable,
);
});
err.emit();
}
// Check if an expression `original_expr_id` comes from the condition of a while loop,
// as opposed from the body of a while loop, which we can naively check by iterating
// parents until we find a loop...
pub(super) fn comes_from_while_condition(
&self,
original_expr_id: HirId,
then: impl FnOnce(&hir::Expr<'_>),
) {
let mut parent = self.tcx.hir().get_parent_node(original_expr_id);
while let Some(node) = self.tcx.hir().find(parent) { while let Some(node) = self.tcx.hir().find(parent) {
match node { match node {
hir::Node::Expr(hir::Expr { hir::Node::Expr(hir::Expr {
@ -863,8 +883,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
), ),
.. ..
}) => { }) => {
// Check if our lhs is a child of the condition of a while loop // Check if our original expression is a child of the condition of a while loop
let expr_is_ancestor = std::iter::successors(Some(lhs.hir_id), |id| { let expr_is_ancestor = std::iter::successors(Some(original_expr_id), |id| {
self.tcx.hir().find_parent_node(*id) self.tcx.hir().find_parent_node(*id)
}) })
.take_while(|id| *id != parent) .take_while(|id| *id != parent)
@ -872,12 +892,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// if it is, then we have a situation like `while Some(0) = value.get(0) {`, // if it is, then we have a situation like `while Some(0) = value.get(0) {`,
// where `while let` was more likely intended. // where `while let` was more likely intended.
if expr_is_ancestor { if expr_is_ancestor {
err.span_suggestion_verbose( then(expr);
expr.span.shrink_to_lo(),
"you might have meant to use pattern destructuring",
"let ".to_string(),
Applicability::MachineApplicable,
);
} }
break; break;
} }
@ -890,8 +905,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} }
} }
} }
err.emit();
} }
// A generic function for checking the 'then' and 'else' clauses in an 'if' // A generic function for checking the 'then' and 'else' clauses in an 'if'

View file

@ -770,55 +770,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let prev_diverges = self.diverges.get(); let prev_diverges = self.diverges.get();
let ctxt = BreakableCtxt { coerce: Some(coerce), may_break: false }; let ctxt = BreakableCtxt { coerce: Some(coerce), may_break: false };
let (ctxt, ()) = self.with_breakable_ctxt(blk.hir_id, ctxt, || { let (ctxt, ()) =
for (pos, s) in blk.stmts.iter().enumerate() { self.with_breakable_ctxt(blk.hir_id, ctxt, || {
self.check_stmt(s, blk.stmts.len() - 1 == pos); for (pos, s) in blk.stmts.iter().enumerate() {
} self.check_stmt(s, blk.stmts.len() - 1 == pos);
}
// check the tail expression **without** holding the // check the tail expression **without** holding the
// `enclosing_breakables` lock below. // `enclosing_breakables` lock below.
let tail_expr_ty = tail_expr.map(|t| self.check_expr_with_expectation(t, expected)); let tail_expr_ty = tail_expr.map(|t| self.check_expr_with_expectation(t, expected));
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
let ctxt = enclosing_breakables.find_breakable(blk.hir_id); let ctxt = enclosing_breakables.find_breakable(blk.hir_id);
let coerce = ctxt.coerce.as_mut().unwrap(); let coerce = ctxt.coerce.as_mut().unwrap();
if let Some(tail_expr_ty) = tail_expr_ty { if let Some(tail_expr_ty) = tail_expr_ty {
let tail_expr = tail_expr.unwrap(); let tail_expr = tail_expr.unwrap();
let span = self.get_expr_coercion_span(tail_expr); let span = self.get_expr_coercion_span(tail_expr);
let cause = self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id)); let cause =
coerce.coerce(self, &cause, tail_expr, tail_expr_ty); self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id));
} else { coerce.coerce(self, &cause, tail_expr, tail_expr_ty);
// Subtle: if there is no explicit tail expression, } else {
// that is typically equivalent to a tail expression // Subtle: if there is no explicit tail expression,
// of `()` -- except if the block diverges. In that // that is typically equivalent to a tail expression
// case, there is no value supplied from the tail // of `()` -- except if the block diverges. In that
// expression (assuming there are no other breaks, // case, there is no value supplied from the tail
// this implies that the type of the block will be // expression (assuming there are no other breaks,
// `!`). // this implies that the type of the block will be
// // `!`).
// #41425 -- label the implicit `()` as being the //
// "found type" here, rather than the "expected type". // #41425 -- label the implicit `()` as being the
if !self.diverges.get().is_always() { // "found type" here, rather than the "expected type".
// #50009 -- Do not point at the entire fn block span, point at the return type if !self.diverges.get().is_always() {
// span, as it is the cause of the requirement, and // #50009 -- Do not point at the entire fn block span, point at the return type
// `consider_hint_about_removing_semicolon` will point at the last expression // span, as it is the cause of the requirement, and
// if it were a relevant part of the error. This improves usability in editors // `consider_hint_about_removing_semicolon` will point at the last expression
// that highlight errors inline. // if it were a relevant part of the error. This improves usability in editors
let mut sp = blk.span; // that highlight errors inline.
let mut fn_span = None; let mut sp = blk.span;
if let Some((decl, ident)) = self.get_parent_fn_decl(blk.hir_id) { let mut fn_span = None;
let ret_sp = decl.output.span(); if let Some((decl, ident)) = self.get_parent_fn_decl(blk.hir_id) {
if let Some(block_sp) = self.parent_item_span(blk.hir_id) { let ret_sp = decl.output.span();
// HACK: on some cases (`ui/liveness/liveness-issue-2163.rs`) the if let Some(block_sp) = self.parent_item_span(blk.hir_id) {
// output would otherwise be incorrect and even misleading. Make sure // HACK: on some cases (`ui/liveness/liveness-issue-2163.rs`) the
// the span we're aiming at correspond to a `fn` body. // output would otherwise be incorrect and even misleading. Make sure
if block_sp == blk.span { // the span we're aiming at correspond to a `fn` body.
sp = ret_sp; if block_sp == blk.span {
fn_span = Some(ident.span); sp = ret_sp;
fn_span = Some(ident.span);
}
} }
} }
} coerce.coerce_forced_unit(
coerce.coerce_forced_unit(
self, self,
&self.misc(sp), &self.misc(sp),
&mut |err| { &mut |err| {
@ -827,19 +829,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if expected_ty == self.tcx.types.bool { if expected_ty == self.tcx.types.bool {
// If this is caused by a missing `let` in a `while let`, // If this is caused by a missing `let` in a `while let`,
// silence this redundant error, as we already emit E0070. // silence this redundant error, as we already emit E0070.
let parent = self.tcx.hir().get_parent_node(blk.hir_id);
let parent = self.tcx.hir().get_parent_node(parent); // Our block must be a `assign desugar local; assignment`
let parent = self.tcx.hir().get_parent_node(parent); if let Some(hir::Node::Block(hir::Block {
let parent = self.tcx.hir().get_parent_node(parent); stmts:
let parent = self.tcx.hir().get_parent_node(parent); [hir::Stmt {
match self.tcx.hir().find(parent) { kind:
Some(hir::Node::Expr(hir::Expr { hir::StmtKind::Local(hir::Local {
kind: hir::ExprKind::Loop(_, _, hir::LoopSource::While, _), source: hir::LocalSource::AssignDesugar(_),
.. ..
})) => { }),
..
}, hir::Stmt {
kind:
hir::StmtKind::Expr(hir::Expr {
kind: hir::ExprKind::Assign(..),
..
}),
..
}],
..
})) = self.tcx.hir().find(blk.hir_id)
{
self.comes_from_while_condition(blk.hir_id, |_| {
err.downgrade_to_delayed_bug(); err.downgrade_to_delayed_bug();
} })
_ => {}
} }
} }
} }
@ -853,9 +867,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}, },
false, false,
); );
}
} }
} });
});
if ctxt.may_break { if ctxt.may_break {
// If we can break from the block, then the block's exit is always reachable // If we can break from the block, then the block's exit is always reachable

View file

@ -0,0 +1,4 @@
fn main() {
while {} {}
//~^ ERROR mismatched types [E0308]
}

View file

@ -0,0 +1,9 @@
error[E0308]: mismatched types
--> $DIR/while-loop-block-cond.rs:2:11
|
LL | while {} {}
| ^^ expected `bool`, found `()`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.