Merge #4505
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:
commit
c0bcaea466
@ -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]
|
||||||
|
@ -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> {
|
||||||
|
@ -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 } => {
|
||||||
|
@ -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': !
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user