4505: Infer return type of loops with value breaks r=flodiebold a=ruabmbua

Creates a type variable to represent the return value of the loop.
Uses `coerce_merge_branch` on each break with the previous value, to determine the actual return value of the loop.

Resolves: https://github.com/rust-analyzer/rust-analyzer/issues/4492 , https://github.com/rust-analyzer/rust-analyzer/issues/4512

Co-authored-by: Roland Ruckerbauer <roland.rucky@gmail.com>
This commit is contained in:
bors[bot] 2020-05-20 07:22:53 +00:00 committed by GitHub
commit c0bcaea466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 30 deletions

View file

@ -1946,6 +1946,23 @@ mod tests {
check_no_diagnostic(content);
}
#[test]
fn expr_diverges_missing_arm() {
let content = r"
enum Either {
A,
B,
}
fn test_fn() {
match loop {} {
Either::A => (),
}
}
";
check_no_diagnostic(content);
}
}
#[cfg(test)]
@ -1997,26 +2014,6 @@ mod false_negatives {
check_no_diagnostic(content);
}
#[test]
fn expr_diverges_missing_arm() {
let content = r"
enum Either {
A,
B,
}
fn test_fn() {
match loop {} {
Either::A => (),
}
}
";
// This is a false negative.
// Even though the match expression diverges, rustc fails
// to compile here since `Either::B` is missing.
check_no_diagnostic(content);
}
#[test]
fn expr_loop_missing_arm() {
let content = r"
@ -2035,7 +2032,7 @@ mod false_negatives {
// We currently infer the type of `loop { break Foo::A }` to `!`, which
// causes us to skip the diagnostic since `Either::A` doesn't type check
// with `!`.
check_no_diagnostic(content);
check_diagnostic(content);
}
#[test]

View file

@ -218,6 +218,7 @@ struct InferenceContext<'a> {
#[derive(Clone, Debug)]
struct BreakableContext {
pub may_break: bool,
pub break_ty: Ty,
}
impl<'a> InferenceContext<'a> {

View file

@ -93,22 +93,25 @@ impl<'a> InferenceContext<'a> {
Ty::Unknown
}
Expr::Loop { body } => {
self.breakables.push(BreakableContext { may_break: false });
self.breakables.push(BreakableContext {
may_break: false,
break_ty: self.table.new_type_var(),
});
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
let ctxt = self.breakables.pop().expect("breakable stack broken");
if ctxt.may_break {
self.diverges = Diverges::Maybe;
}
// FIXME handle break with value
if ctxt.may_break {
Ty::unit()
ctxt.break_ty
} else {
Ty::simple(TypeCtor::Never)
}
}
Expr::While { condition, body } => {
self.breakables.push(BreakableContext { may_break: false });
self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
// while let is desugared to a match loop, so this is always simple while
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
@ -120,7 +123,7 @@ impl<'a> InferenceContext<'a> {
Expr::For { iterable, body, pat } => {
let iterable_ty = self.infer_expr(*iterable, &Expectation::none());
self.breakables.push(BreakableContext { may_break: false });
self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
let pat_ty =
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
@ -229,17 +232,29 @@ impl<'a> InferenceContext<'a> {
}
Expr::Continue => Ty::simple(TypeCtor::Never),
Expr::Break { expr } => {
if let Some(expr) = expr {
// FIXME handle break with value
self.infer_expr(*expr, &Expectation::none());
}
let val_ty = if let Some(expr) = expr {
self.infer_expr(*expr, &Expectation::none())
} else {
Ty::unit()
};
let last_ty = if let Some(ctxt) = self.breakables.last() {
ctxt.break_ty.clone()
} else {
Ty::Unknown
};
let merged_type = self.coerce_merge_branch(&last_ty, &val_ty);
if let Some(ctxt) = self.breakables.last_mut() {
ctxt.break_ty = merged_type;
ctxt.may_break = true;
} else {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr,
});
}
Ty::simple(TypeCtor::Never)
}
Expr::Return { expr } => {

View file

@ -1860,3 +1860,66 @@ fn test() {
"###
);
}
#[test]
fn infer_loop_break_with_val() {
assert_snapshot!(
infer(r#"
enum Option<T> { Some(T), None }
use Option::*;
fn test() {
let x = loop {
if false {
break None;
}
break Some(true);
};
}
"#),
@r###"
60..169 '{ ... }; }': ()
70..71 'x': Option<bool>
74..166 'loop {... }': Option<bool>
79..166 '{ ... }': ()
89..133 'if fal... }': ()
92..97 'false': bool
98..133 '{ ... }': ()
112..122 'break None': !
118..122 'None': Option<bool>
143..159 'break ...(true)': !
149..153 'Some': Some<bool>(bool) -> Option<bool>
149..159 'Some(true)': Option<bool>
154..158 'true': bool
"###
);
}
#[test]
fn infer_loop_break_without_val() {
assert_snapshot!(
infer(r#"
enum Option<T> { Some(T), None }
use Option::*;
fn test() {
let x = loop {
if false {
break;
}
};
}
"#),
@r###"
60..137 '{ ... }; }': ()
70..71 'x': ()
74..134 'loop {... }': ()
79..134 '{ ... }': ()
89..128 'if fal... }': ()
92..97 'false': bool
98..128 '{ ... }': ()
112..117 'break': !
"###
);
}