Skip to content

Commit d12dc74

Browse files
committed
Auto merge of rust-lang#118072 - estebank:issue-98982, r=cjgillot
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 rust-lang#98982.
2 parents db07ccc + 45efb8e commit d12dc74

File tree

6 files changed

+134
-30
lines changed

6 files changed

+134
-30
lines changed

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};
@@ -1639,12 +1640,15 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
16391640
None,
16401641
Some(coercion_error),
16411642
);
1642-
}
1643-
1644-
if visitor.ret_exprs.len() > 0
1645-
&& let Some(expr) = expression
1646-
{
1647-
self.note_unreachable_loop_return(&mut err, expr, &visitor.ret_exprs);
1643+
if visitor.ret_exprs.len() > 0 {
1644+
self.note_unreachable_loop_return(
1645+
&mut err,
1646+
fcx.tcx,
1647+
&expr,
1648+
&visitor.ret_exprs,
1649+
expected,
1650+
);
1651+
}
16481652
}
16491653

16501654
let reported = err.emit_unless(unsized_return);
@@ -1657,8 +1661,10 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
16571661
fn note_unreachable_loop_return(
16581662
&self,
16591663
err: &mut Diagnostic,
1664+
tcx: TyCtxt<'tcx>,
16601665
expr: &hir::Expr<'tcx>,
16611666
ret_exprs: &Vec<&'tcx hir::Expr<'tcx>>,
1667+
ty: Ty<'tcx>,
16621668
) {
16631669
let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else {
16641670
return;
@@ -1683,10 +1689,77 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
16831689
ret_exprs.len() - MAXITER
16841690
));
16851691
}
1686-
err.help(
1687-
"return a value for the case when the loop has zero elements to iterate on, or \
1688-
consider changing the return type to account for that possibility",
1689-
);
1692+
let hir = tcx.hir();
1693+
let item = hir.get_parent_item(expr.hir_id);
1694+
let ret_msg = "return a value for the case when the loop has zero elements to iterate on";
1695+
let ret_ty_msg =
1696+
"otherwise consider changing the return type to account for that possibility";
1697+
if let Some(node) = hir.find(item.into())
1698+
&& let Some(body_id) = node.body_id()
1699+
&& let Some(sig) = node.fn_sig()
1700+
&& let hir::ExprKind::Block(block, _) = hir.body(body_id).value.kind
1701+
&& !ty.is_never()
1702+
{
1703+
let indentation = if let None = block.expr
1704+
&& let [.., last] = &block.stmts[..]
1705+
{
1706+
tcx.sess.source_map().indentation_before(last.span).unwrap_or_else(String::new)
1707+
} else if let Some(expr) = block.expr {
1708+
tcx.sess.source_map().indentation_before(expr.span).unwrap_or_else(String::new)
1709+
} else {
1710+
String::new()
1711+
};
1712+
if let None = block.expr
1713+
&& let [.., last] = &block.stmts[..]
1714+
{
1715+
err.span_suggestion_verbose(
1716+
last.span.shrink_to_hi(),
1717+
ret_msg,
1718+
format!("\n{indentation}/* `{ty}` value */"),
1719+
Applicability::MaybeIncorrect,
1720+
);
1721+
} else if let Some(expr) = block.expr {
1722+
err.span_suggestion_verbose(
1723+
expr.span.shrink_to_hi(),
1724+
ret_msg,
1725+
format!("\n{indentation}/* `{ty}` value */"),
1726+
Applicability::MaybeIncorrect,
1727+
);
1728+
}
1729+
let mut sugg = match sig.decl.output {
1730+
hir::FnRetTy::DefaultReturn(span) => {
1731+
vec![(span, " -> Option<()>".to_string())]
1732+
}
1733+
hir::FnRetTy::Return(ty) => {
1734+
vec![
1735+
(ty.span.shrink_to_lo(), "Option<".to_string()),
1736+
(ty.span.shrink_to_hi(), ">".to_string()),
1737+
]
1738+
}
1739+
};
1740+
for ret_expr in ret_exprs {
1741+
match ret_expr.kind {
1742+
hir::ExprKind::Ret(Some(expr)) => {
1743+
sugg.push((expr.span.shrink_to_lo(), "Some(".to_string()));
1744+
sugg.push((expr.span.shrink_to_hi(), ")".to_string()));
1745+
}
1746+
hir::ExprKind::Ret(None) => {
1747+
sugg.push((ret_expr.span.shrink_to_hi(), " Some(())".to_string()));
1748+
}
1749+
_ => {}
1750+
}
1751+
}
1752+
if let None = block.expr
1753+
&& let [.., last] = &block.stmts[..]
1754+
{
1755+
sugg.push((last.span.shrink_to_hi(), format!("\n{indentation}None")));
1756+
} else if let Some(expr) = block.expr {
1757+
sugg.push((expr.span.shrink_to_hi(), format!("\n{indentation}None")));
1758+
}
1759+
err.multipart_suggestion(ret_ty_msg, sugg, Applicability::MaybeIncorrect);
1760+
} else {
1761+
err.help(format!("{ret_msg}, {ret_ty_msg}"));
1762+
}
16901763
}
16911764

16921765
fn report_return_mismatched_types<'a>(

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

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() {}

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 1 previous error
3358

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() {}

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 1 previous error
2333

0 commit comments

Comments
 (0)