Skip to content

Commit c0bcaea

Browse files
bors[bot]ruabmbua
andauthored
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: #4492 , #4512 Co-authored-by: Roland Ruckerbauer <roland.rucky@gmail.com>
2 parents efac093 + 45021ca commit c0bcaea

File tree

4 files changed

+106
-30
lines changed

4 files changed

+106
-30
lines changed

crates/ra_hir_ty/src/_match.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,6 +1946,23 @@ mod tests {
19461946

19471947
check_no_diagnostic(content);
19481948
}
1949+
1950+
#[test]
1951+
fn expr_diverges_missing_arm() {
1952+
let content = r"
1953+
enum Either {
1954+
A,
1955+
B,
1956+
}
1957+
fn test_fn() {
1958+
match loop {} {
1959+
Either::A => (),
1960+
}
1961+
}
1962+
";
1963+
1964+
check_no_diagnostic(content);
1965+
}
19491966
}
19501967

19511968
#[cfg(test)]
@@ -1997,26 +2014,6 @@ mod false_negatives {
19972014
check_no_diagnostic(content);
19982015
}
19992016

2000-
#[test]
2001-
fn expr_diverges_missing_arm() {
2002-
let content = r"
2003-
enum Either {
2004-
A,
2005-
B,
2006-
}
2007-
fn test_fn() {
2008-
match loop {} {
2009-
Either::A => (),
2010-
}
2011-
}
2012-
";
2013-
2014-
// This is a false negative.
2015-
// Even though the match expression diverges, rustc fails
2016-
// to compile here since `Either::B` is missing.
2017-
check_no_diagnostic(content);
2018-
}
2019-
20202017
#[test]
20212018
fn expr_loop_missing_arm() {
20222019
let content = r"
@@ -2035,7 +2032,7 @@ mod false_negatives {
20352032
// We currently infer the type of `loop { break Foo::A }` to `!`, which
20362033
// causes us to skip the diagnostic since `Either::A` doesn't type check
20372034
// with `!`.
2038-
check_no_diagnostic(content);
2035+
check_diagnostic(content);
20392036
}
20402037

20412038
#[test]

crates/ra_hir_ty/src/infer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ struct InferenceContext<'a> {
218218
#[derive(Clone, Debug)]
219219
struct BreakableContext {
220220
pub may_break: bool,
221+
pub break_ty: Ty,
221222
}
222223

223224
impl<'a> InferenceContext<'a> {

crates/ra_hir_ty/src/infer/expr.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,25 @@ impl<'a> InferenceContext<'a> {
9393
Ty::Unknown
9494
}
9595
Expr::Loop { body } => {
96-
self.breakables.push(BreakableContext { may_break: false });
96+
self.breakables.push(BreakableContext {
97+
may_break: false,
98+
break_ty: self.table.new_type_var(),
99+
});
97100
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
98101

99102
let ctxt = self.breakables.pop().expect("breakable stack broken");
100103
if ctxt.may_break {
101104
self.diverges = Diverges::Maybe;
102105
}
103-
// FIXME handle break with value
106+
104107
if ctxt.may_break {
105-
Ty::unit()
108+
ctxt.break_ty
106109
} else {
107110
Ty::simple(TypeCtor::Never)
108111
}
109112
}
110113
Expr::While { condition, body } => {
111-
self.breakables.push(BreakableContext { may_break: false });
114+
self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
112115
// while let is desugared to a match loop, so this is always simple while
113116
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
114117
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
@@ -120,7 +123,7 @@ impl<'a> InferenceContext<'a> {
120123
Expr::For { iterable, body, pat } => {
121124
let iterable_ty = self.infer_expr(*iterable, &Expectation::none());
122125

123-
self.breakables.push(BreakableContext { may_break: false });
126+
self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
124127
let pat_ty =
125128
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
126129

@@ -229,17 +232,29 @@ impl<'a> InferenceContext<'a> {
229232
}
230233
Expr::Continue => Ty::simple(TypeCtor::Never),
231234
Expr::Break { expr } => {
232-
if let Some(expr) = expr {
233-
// FIXME handle break with value
234-
self.infer_expr(*expr, &Expectation::none());
235-
}
235+
let val_ty = if let Some(expr) = expr {
236+
self.infer_expr(*expr, &Expectation::none())
237+
} else {
238+
Ty::unit()
239+
};
240+
241+
let last_ty = if let Some(ctxt) = self.breakables.last() {
242+
ctxt.break_ty.clone()
243+
} else {
244+
Ty::Unknown
245+
};
246+
247+
let merged_type = self.coerce_merge_branch(&last_ty, &val_ty);
248+
236249
if let Some(ctxt) = self.breakables.last_mut() {
250+
ctxt.break_ty = merged_type;
237251
ctxt.may_break = true;
238252
} else {
239253
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
240254
expr: tgt_expr,
241255
});
242256
}
257+
243258
Ty::simple(TypeCtor::Never)
244259
}
245260
Expr::Return { expr } => {

crates/ra_hir_ty/src/tests/simple.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,3 +1860,66 @@ fn test() {
18601860
"###
18611861
);
18621862
}
1863+
1864+
#[test]
1865+
fn infer_loop_break_with_val() {
1866+
assert_snapshot!(
1867+
infer(r#"
1868+
enum Option<T> { Some(T), None }
1869+
use Option::*;
1870+
1871+
fn test() {
1872+
let x = loop {
1873+
if false {
1874+
break None;
1875+
}
1876+
1877+
break Some(true);
1878+
};
1879+
}
1880+
"#),
1881+
@r###"
1882+
60..169 '{ ... }; }': ()
1883+
70..71 'x': Option<bool>
1884+
74..166 'loop {... }': Option<bool>
1885+
79..166 '{ ... }': ()
1886+
89..133 'if fal... }': ()
1887+
92..97 'false': bool
1888+
98..133 '{ ... }': ()
1889+
112..122 'break None': !
1890+
118..122 'None': Option<bool>
1891+
143..159 'break ...(true)': !
1892+
149..153 'Some': Some<bool>(bool) -> Option<bool>
1893+
149..159 'Some(true)': Option<bool>
1894+
154..158 'true': bool
1895+
"###
1896+
);
1897+
}
1898+
1899+
#[test]
1900+
fn infer_loop_break_without_val() {
1901+
assert_snapshot!(
1902+
infer(r#"
1903+
enum Option<T> { Some(T), None }
1904+
use Option::*;
1905+
1906+
fn test() {
1907+
let x = loop {
1908+
if false {
1909+
break;
1910+
}
1911+
};
1912+
}
1913+
"#),
1914+
@r###"
1915+
60..137 '{ ... }; }': ()
1916+
70..71 'x': ()
1917+
74..134 'loop {... }': ()
1918+
79..134 '{ ... }': ()
1919+
89..128 'if fal... }': ()
1920+
92..97 'false': bool
1921+
98..128 '{ ... }': ()
1922+
112..117 'break': !
1923+
"###
1924+
);
1925+
}

0 commit comments

Comments
 (0)