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); 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)] #[cfg(test)]
@ -1997,26 +2014,6 @@ mod false_negatives {
check_no_diagnostic(content); 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] #[test]
fn expr_loop_missing_arm() { fn expr_loop_missing_arm() {
let content = r" let content = r"
@ -2035,7 +2032,7 @@ mod false_negatives {
// We currently infer the type of `loop { break Foo::A }` to `!`, which // 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 // causes us to skip the diagnostic since `Either::A` doesn't type check
// with `!`. // with `!`.
check_no_diagnostic(content); check_diagnostic(content);
} }
#[test] #[test]

View File

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

View File

@ -93,22 +93,25 @@ impl<'a> InferenceContext<'a> {
Ty::Unknown Ty::Unknown
} }
Expr::Loop { body } => { 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())); self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
let ctxt = self.breakables.pop().expect("breakable stack broken"); let ctxt = self.breakables.pop().expect("breakable stack broken");
if ctxt.may_break { if ctxt.may_break {
self.diverges = Diverges::Maybe; self.diverges = Diverges::Maybe;
} }
// FIXME handle break with value
if ctxt.may_break { if ctxt.may_break {
Ty::unit() ctxt.break_ty
} else { } else {
Ty::simple(TypeCtor::Never) Ty::simple(TypeCtor::Never)
} }
} }
Expr::While { condition, body } => { 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 // 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(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
self.infer_expr(*body, &Expectation::has_type(Ty::unit())); self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
@ -120,7 +123,7 @@ impl<'a> InferenceContext<'a> {
Expr::For { iterable, body, pat } => { Expr::For { iterable, body, pat } => {
let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); 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 = let pat_ty =
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); 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::Continue => Ty::simple(TypeCtor::Never),
Expr::Break { expr } => { Expr::Break { expr } => {
if let Some(expr) = expr { let val_ty = if let Some(expr) = expr {
// FIXME handle break with value self.infer_expr(*expr, &Expectation::none())
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() { if let Some(ctxt) = self.breakables.last_mut() {
ctxt.break_ty = merged_type;
ctxt.may_break = true; ctxt.may_break = true;
} else { } else {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr, expr: tgt_expr,
}); });
} }
Ty::simple(TypeCtor::Never) Ty::simple(TypeCtor::Never)
} }
Expr::Return { expr } => { 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': !
"###
);
}