Skip to content

Commit 45efb8e

Browse files
committed
Provide structured suggestion for type mismatch in loop
We currently provide only a `help` message, this PR introduces the last two structured suggestions instead: ``` error[E0308]: mismatched types --> $DIR/issue-98982.rs:2:5 | LL | fn foo() -> i32 { | --- expected `i32` because of return type LL | / for i in 0..0 { LL | | return i; LL | | } | |_____^ expected `i32`, found `()` | note: the function expects a value to always be returned, but loops might run zero times --> $DIR/issue-98982.rs:2:5 | LL | for i in 0..0 { | ^^^^^^^^^^^^^ this might have zero elements to iterate on LL | return i; | -------- if the loop doesn't execute, this value would never get returned help: return a value for the case when the loop has zero elements to iterate on | LL ~ } LL ~ /* `i32` value */ | help: otherwise consider changing the return type to account for that possibility | LL ~ fn foo() -> Option<i32> { LL | for i in 0..0 { LL ~ return Some(i); LL ~ } LL ~ None | ``` Fix #98982.
1 parent 73bc121 commit 45efb8e

File tree

6 files changed

+134
-30
lines changed

6 files changed

+134
-30
lines changed

Diff for: compiler/rustc_hir_typeck/src/coercion.rs

+86-13
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
//! ```
3737
3838
use crate::FnCtxt;
39-
use rustc_errors::{struct_span_err, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
39+
use rustc_errors::{
40+
struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
41+
};
4042
use rustc_hir as hir;
4143
use rustc_hir::def_id::DefId;
4244
use rustc_hir::intravisit::{self, Visitor};
@@ -53,8 +55,7 @@ use rustc_middle::ty::adjustment::{
5355
use rustc_middle::ty::error::TypeError;
5456
use rustc_middle::ty::relate::RelateResult;
5557
use rustc_middle::ty::visit::TypeVisitableExt;
56-
use rustc_middle::ty::GenericArgsRef;
57-
use rustc_middle::ty::{self, Ty, TypeAndMut};
58+
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeAndMut};
5859
use rustc_session::parse::feature_err;
5960
use rustc_span::symbol::sym;
6061
use rustc_span::{self, DesugaringKind};
@@ -1660,12 +1661,15 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
16601661
None,
16611662
Some(coercion_error),
16621663
);
1663-
}
1664-
1665-
if visitor.ret_exprs.len() > 0
1666-
&& let Some(expr) = expression
1667-
{
1668-
self.note_unreachable_loop_return(&mut err, expr, &visitor.ret_exprs);
1664+
if visitor.ret_exprs.len() > 0 {
1665+
self.note_unreachable_loop_return(
1666+
&mut err,
1667+
fcx.tcx,
1668+
&expr,
1669+
&visitor.ret_exprs,
1670+
expected,
1671+
);
1672+
}
16691673
}
16701674

16711675
let reported = err.emit_unless(unsized_return);
@@ -1678,8 +1682,10 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
16781682
fn note_unreachable_loop_return(
16791683
&self,
16801684
err: &mut Diagnostic,
1685+
tcx: TyCtxt<'tcx>,
16811686
expr: &hir::Expr<'tcx>,
16821687
ret_exprs: &Vec<&'tcx hir::Expr<'tcx>>,
1688+
ty: Ty<'tcx>,
16831689
) {
16841690
let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else {
16851691
return;
@@ -1704,10 +1710,77 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
17041710
ret_exprs.len() - MAXITER
17051711
));
17061712
}
1707-
err.help(
1708-
"return a value for the case when the loop has zero elements to iterate on, or \
1709-
consider changing the return type to account for that possibility",
1710-
);
1713+
let hir = tcx.hir();
1714+
let item = hir.get_parent_item(expr.hir_id);
1715+
let ret_msg = "return a value for the case when the loop has zero elements to iterate on";
1716+
let ret_ty_msg =
1717+
"otherwise consider changing the return type to account for that possibility";
1718+
if let Some(node) = hir.find(item.into())
1719+
&& let Some(body_id) = node.body_id()
1720+
&& let Some(sig) = node.fn_sig()
1721+
&& let hir::ExprKind::Block(block, _) = hir.body(body_id).value.kind
1722+
&& !ty.is_never()
1723+
{
1724+
let indentation = if let None = block.expr
1725+
&& let [.., last] = &block.stmts[..]
1726+
{
1727+
tcx.sess.source_map().indentation_before(last.span).unwrap_or_else(String::new)
1728+
} else if let Some(expr) = block.expr {
1729+
tcx.sess.source_map().indentation_before(expr.span).unwrap_or_else(String::new)
1730+
} else {
1731+
String::new()
1732+
};
1733+
if let None = block.expr
1734+
&& let [.., last] = &block.stmts[..]
1735+
{
1736+
err.span_suggestion_verbose(
1737+
last.span.shrink_to_hi(),
1738+
ret_msg,
1739+
format!("\n{indentation}/* `{ty}` value */"),
1740+
Applicability::MaybeIncorrect,
1741+
);
1742+
} else if let Some(expr) = block.expr {
1743+
err.span_suggestion_verbose(
1744+
expr.span.shrink_to_hi(),
1745+
ret_msg,
1746+
format!("\n{indentation}/* `{ty}` value */"),
1747+
Applicability::MaybeIncorrect,
1748+
);
1749+
}
1750+
let mut sugg = match sig.decl.output {
1751+
hir::FnRetTy::DefaultReturn(span) => {
1752+
vec![(span, " -> Option<()>".to_string())]
1753+
}
1754+
hir::FnRetTy::Return(ty) => {
1755+
vec![
1756+
(ty.span.shrink_to_lo(), "Option<".to_string()),
1757+
(ty.span.shrink_to_hi(), ">".to_string()),
1758+
]
1759+
}
1760+
};
1761+
for ret_expr in ret_exprs {
1762+
match ret_expr.kind {
1763+
hir::ExprKind::Ret(Some(expr)) => {
1764+
sugg.push((expr.span.shrink_to_lo(), "Some(".to_string()));
1765+
sugg.push((expr.span.shrink_to_hi(), ")".to_string()));
1766+
}
1767+
hir::ExprKind::Ret(None) => {
1768+
sugg.push((ret_expr.span.shrink_to_hi(), " Some(())".to_string()));
1769+
}
1770+
_ => {}
1771+
}
1772+
}
1773+
if let None = block.expr
1774+
&& let [.., last] = &block.stmts[..]
1775+
{
1776+
sugg.push((last.span.shrink_to_hi(), format!("\n{indentation}None")));
1777+
} else if let Some(expr) = block.expr {
1778+
sugg.push((expr.span.shrink_to_hi(), format!("\n{indentation}None")));
1779+
}
1780+
err.multipart_suggestion(ret_ty_msg, sugg, Applicability::MaybeIncorrect);
1781+
} else {
1782+
err.help(format!("{ret_msg}, {ret_ty_msg}"));
1783+
}
17111784
}
17121785

17131786
fn report_return_mismatched_types<'a>(

Diff for: tests/ui/for-loop-while/break-while-condition.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ LL | while false {
3838
| ^^^^^^^^^^^ this might have zero elements to iterate on
3939
LL | return
4040
| ------ if the loop doesn't execute, this value would never get returned
41-
= help: return a value for the case when the loop has zero elements to iterate on, or consider changing the return type to account for that possibility
41+
= help: return a value for the case when the loop has zero elements to iterate on, otherwise consider changing the return type to account for that possibility
4242

4343
error: aborting due to 3 previous errors
4444

Diff for: tests/ui/typeck/issue-100285.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
fn foo(n: i32) -> i32 {
2-
for i in 0..0 {
3-
//~^ ERROR: mismatched types [E0308]
1+
fn foo(n: i32) -> i32 { //~ HELP otherwise consider changing the return type to account for that possibility
2+
for i in 0..0 { //~ ERROR mismatched types [E0308]
43
if n < 0 {
54
return i;
65
} else if n < 10 {
@@ -15,8 +14,7 @@ fn foo(n: i32) -> i32 {
1514
return 5;
1615
}
1716

18-
}
19-
//~| help: return a value for the case when the loop has zero elements to iterate on, or consider changing the return type to account for that possibility
17+
} //~ HELP return a value for the case when the loop has zero elements to iterate on
2018
}
2119

2220
fn main() {}

Diff for: tests/ui/typeck/issue-100285.stderr

+28-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ error[E0308]: mismatched types
44
LL | fn foo(n: i32) -> i32 {
55
| --- expected `i32` because of return type
66
LL | / for i in 0..0 {
7-
LL | |
87
LL | | if n < 0 {
98
LL | | return i;
9+
LL | | } else if n < 10 {
1010
... |
1111
LL | |
1212
LL | | }
@@ -17,7 +17,7 @@ note: the function expects a value to always be returned, but loops might run ze
1717
|
1818
LL | for i in 0..0 {
1919
| ^^^^^^^^^^^^^ this might have zero elements to iterate on
20-
...
20+
LL | if n < 0 {
2121
LL | return i;
2222
| -------- if the loop doesn't execute, this value would never get returned
2323
LL | } else if n < 10 {
@@ -27,7 +27,32 @@ LL | } else if n < 20 {
2727
LL | return 2;
2828
| -------- if the loop doesn't execute, this value would never get returned
2929
= note: if the loop doesn't execute, 3 other values would never get returned
30-
= help: return a value for the case when the loop has zero elements to iterate on, or consider changing the return type to account for that possibility
30+
help: return a value for the case when the loop has zero elements to iterate on
31+
|
32+
LL ~ }
33+
LL ~ /* `i32` value */
34+
|
35+
help: otherwise consider changing the return type to account for that possibility
36+
|
37+
LL ~ fn foo(n: i32) -> Option<i32> {
38+
LL | for i in 0..0 {
39+
LL | if n < 0 {
40+
LL ~ return Some(i);
41+
LL | } else if n < 10 {
42+
LL ~ return Some(1);
43+
LL | } else if n < 20 {
44+
LL ~ return Some(2);
45+
LL | } else if n < 30 {
46+
LL ~ return Some(3);
47+
LL | } else if n < 40 {
48+
LL ~ return Some(4);
49+
LL | } else {
50+
LL ~ return Some(5);
51+
LL | }
52+
LL |
53+
LL ~ }
54+
LL ~ None
55+
|
3156

3257
error: aborting due to previous error
3358

Diff for: tests/ui/typeck/issue-98982.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
fn foo() -> i32 {
2-
for i in 0..0 {
3-
//~^ ERROR: mismatched types [E0308]
1+
fn foo() -> i32 { //~ HELP otherwise consider changing the return type to account for that possibility
2+
for i in 0..0 { //~ ERROR mismatched types [E0308]
43
return i;
5-
}
6-
//~| help: return a value for the case when the loop has zero elements to iterate on, or consider changing the return type to account for that possibility
4+
} //~ HELP return a value for the case when the loop has zero elements to iterate on
75
}
86

97
fn main() {}

Diff for: tests/ui/typeck/issue-98982.stderr

+13-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ error[E0308]: mismatched types
44
LL | fn foo() -> i32 {
55
| --- expected `i32` because of return type
66
LL | / for i in 0..0 {
7-
LL | |
87
LL | | return i;
98
LL | | }
109
| |_____^ expected `i32`, found `()`
@@ -14,10 +13,21 @@ note: the function expects a value to always be returned, but loops might run ze
1413
|
1514
LL | for i in 0..0 {
1615
| ^^^^^^^^^^^^^ this might have zero elements to iterate on
17-
LL |
1816
LL | return i;
1917
| -------- if the loop doesn't execute, this value would never get returned
20-
= help: return a value for the case when the loop has zero elements to iterate on, or consider changing the return type to account for that possibility
18+
help: return a value for the case when the loop has zero elements to iterate on
19+
|
20+
LL ~ }
21+
LL ~ /* `i32` value */
22+
|
23+
help: otherwise consider changing the return type to account for that possibility
24+
|
25+
LL ~ fn foo() -> Option<i32> {
26+
LL | for i in 0..0 {
27+
LL ~ return Some(i);
28+
LL ~ }
29+
LL ~ None
30+
|
2131

2232
error: aborting due to previous error
2333

0 commit comments

Comments
 (0)