Auto merge of #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 #98982.
This commit is contained in:
commit
d12dc74a2c
@ -36,7 +36,9 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::FnCtxt;
|
use crate::FnCtxt;
|
||||||
use rustc_errors::{struct_span_err, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
|
use rustc_errors::{
|
||||||
|
struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
|
||||||
|
};
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_hir::intravisit::{self, Visitor};
|
use rustc_hir::intravisit::{self, Visitor};
|
||||||
@ -53,8 +55,7 @@
|
|||||||
use rustc_middle::ty::error::TypeError;
|
use rustc_middle::ty::error::TypeError;
|
||||||
use rustc_middle::ty::relate::RelateResult;
|
use rustc_middle::ty::relate::RelateResult;
|
||||||
use rustc_middle::ty::visit::TypeVisitableExt;
|
use rustc_middle::ty::visit::TypeVisitableExt;
|
||||||
use rustc_middle::ty::GenericArgsRef;
|
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeAndMut};
|
||||||
use rustc_middle::ty::{self, Ty, TypeAndMut};
|
|
||||||
use rustc_session::parse::feature_err;
|
use rustc_session::parse::feature_err;
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
use rustc_span::{self, DesugaringKind};
|
use rustc_span::{self, DesugaringKind};
|
||||||
@ -1639,12 +1640,15 @@ pub(crate) fn coerce_inner<'a>(
|
|||||||
None,
|
None,
|
||||||
Some(coercion_error),
|
Some(coercion_error),
|
||||||
);
|
);
|
||||||
}
|
if visitor.ret_exprs.len() > 0 {
|
||||||
|
self.note_unreachable_loop_return(
|
||||||
if visitor.ret_exprs.len() > 0
|
&mut err,
|
||||||
&& let Some(expr) = expression
|
fcx.tcx,
|
||||||
{
|
&expr,
|
||||||
self.note_unreachable_loop_return(&mut err, expr, &visitor.ret_exprs);
|
&visitor.ret_exprs,
|
||||||
|
expected,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let reported = err.emit_unless(unsized_return);
|
let reported = err.emit_unless(unsized_return);
|
||||||
@ -1657,8 +1661,10 @@ pub(crate) fn coerce_inner<'a>(
|
|||||||
fn note_unreachable_loop_return(
|
fn note_unreachable_loop_return(
|
||||||
&self,
|
&self,
|
||||||
err: &mut Diagnostic,
|
err: &mut Diagnostic,
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
expr: &hir::Expr<'tcx>,
|
expr: &hir::Expr<'tcx>,
|
||||||
ret_exprs: &Vec<&'tcx hir::Expr<'tcx>>,
|
ret_exprs: &Vec<&'tcx hir::Expr<'tcx>>,
|
||||||
|
ty: Ty<'tcx>,
|
||||||
) {
|
) {
|
||||||
let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else {
|
let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else {
|
||||||
return;
|
return;
|
||||||
@ -1683,10 +1689,77 @@ fn note_unreachable_loop_return(
|
|||||||
ret_exprs.len() - MAXITER
|
ret_exprs.len() - MAXITER
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
err.help(
|
let hir = tcx.hir();
|
||||||
"return a value for the case when the loop has zero elements to iterate on, or \
|
let item = hir.get_parent_item(expr.hir_id);
|
||||||
consider changing the return type to account for that possibility",
|
let ret_msg = "return a value for the case when the loop has zero elements to iterate on";
|
||||||
);
|
let ret_ty_msg =
|
||||||
|
"otherwise consider changing the return type to account for that possibility";
|
||||||
|
if let Some(node) = hir.find(item.into())
|
||||||
|
&& let Some(body_id) = node.body_id()
|
||||||
|
&& let Some(sig) = node.fn_sig()
|
||||||
|
&& let hir::ExprKind::Block(block, _) = hir.body(body_id).value.kind
|
||||||
|
&& !ty.is_never()
|
||||||
|
{
|
||||||
|
let indentation = if let None = block.expr
|
||||||
|
&& let [.., last] = &block.stmts[..]
|
||||||
|
{
|
||||||
|
tcx.sess.source_map().indentation_before(last.span).unwrap_or_else(String::new)
|
||||||
|
} else if let Some(expr) = block.expr {
|
||||||
|
tcx.sess.source_map().indentation_before(expr.span).unwrap_or_else(String::new)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
if let None = block.expr
|
||||||
|
&& let [.., last] = &block.stmts[..]
|
||||||
|
{
|
||||||
|
err.span_suggestion_verbose(
|
||||||
|
last.span.shrink_to_hi(),
|
||||||
|
ret_msg,
|
||||||
|
format!("\n{indentation}/* `{ty}` value */"),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
} else if let Some(expr) = block.expr {
|
||||||
|
err.span_suggestion_verbose(
|
||||||
|
expr.span.shrink_to_hi(),
|
||||||
|
ret_msg,
|
||||||
|
format!("\n{indentation}/* `{ty}` value */"),
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut sugg = match sig.decl.output {
|
||||||
|
hir::FnRetTy::DefaultReturn(span) => {
|
||||||
|
vec![(span, " -> Option<()>".to_string())]
|
||||||
|
}
|
||||||
|
hir::FnRetTy::Return(ty) => {
|
||||||
|
vec![
|
||||||
|
(ty.span.shrink_to_lo(), "Option<".to_string()),
|
||||||
|
(ty.span.shrink_to_hi(), ">".to_string()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for ret_expr in ret_exprs {
|
||||||
|
match ret_expr.kind {
|
||||||
|
hir::ExprKind::Ret(Some(expr)) => {
|
||||||
|
sugg.push((expr.span.shrink_to_lo(), "Some(".to_string()));
|
||||||
|
sugg.push((expr.span.shrink_to_hi(), ")".to_string()));
|
||||||
|
}
|
||||||
|
hir::ExprKind::Ret(None) => {
|
||||||
|
sugg.push((ret_expr.span.shrink_to_hi(), " Some(())".to_string()));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let None = block.expr
|
||||||
|
&& let [.., last] = &block.stmts[..]
|
||||||
|
{
|
||||||
|
sugg.push((last.span.shrink_to_hi(), format!("\n{indentation}None")));
|
||||||
|
} else if let Some(expr) = block.expr {
|
||||||
|
sugg.push((expr.span.shrink_to_hi(), format!("\n{indentation}None")));
|
||||||
|
}
|
||||||
|
err.multipart_suggestion(ret_ty_msg, sugg, Applicability::MaybeIncorrect);
|
||||||
|
} else {
|
||||||
|
err.help(format!("{ret_msg}, {ret_ty_msg}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_return_mismatched_types<'a>(
|
fn report_return_mismatched_types<'a>(
|
||||||
|
@ -38,7 +38,7 @@ LL | while false {
|
|||||||
| ^^^^^^^^^^^ this might have zero elements to iterate on
|
| ^^^^^^^^^^^ this might have zero elements to iterate on
|
||||||
LL | return
|
LL | return
|
||||||
| ------ if the loop doesn't execute, this value would never get returned
|
| ------ 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, or consider changing the return type to account for that possibility
|
= 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
|
||||||
|
|
||||||
error: aborting due to 3 previous errors
|
error: aborting due to 3 previous errors
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
fn foo(n: i32) -> i32 {
|
fn foo(n: i32) -> i32 { //~ HELP otherwise consider changing the return type to account for that possibility
|
||||||
for i in 0..0 {
|
for i in 0..0 { //~ ERROR mismatched types [E0308]
|
||||||
//~^ ERROR: mismatched types [E0308]
|
|
||||||
if n < 0 {
|
if n < 0 {
|
||||||
return i;
|
return i;
|
||||||
} else if n < 10 {
|
} else if n < 10 {
|
||||||
@ -15,8 +14,7 @@ fn foo(n: i32) -> i32 {
|
|||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} //~ HELP return a value for the case when the loop has zero elements to iterate on
|
||||||
//~| 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -4,9 +4,9 @@ error[E0308]: mismatched types
|
|||||||
LL | fn foo(n: i32) -> i32 {
|
LL | fn foo(n: i32) -> i32 {
|
||||||
| --- expected `i32` because of return type
|
| --- expected `i32` because of return type
|
||||||
LL | / for i in 0..0 {
|
LL | / for i in 0..0 {
|
||||||
LL | |
|
|
||||||
LL | | if n < 0 {
|
LL | | if n < 0 {
|
||||||
LL | | return i;
|
LL | | return i;
|
||||||
|
LL | | } else if n < 10 {
|
||||||
... |
|
... |
|
||||||
LL | |
|
LL | |
|
||||||
LL | | }
|
LL | | }
|
||||||
@ -17,7 +17,7 @@ note: the function expects a value to always be returned, but loops might run ze
|
|||||||
|
|
|
|
||||||
LL | for i in 0..0 {
|
LL | for i in 0..0 {
|
||||||
| ^^^^^^^^^^^^^ this might have zero elements to iterate on
|
| ^^^^^^^^^^^^^ this might have zero elements to iterate on
|
||||||
...
|
LL | if n < 0 {
|
||||||
LL | return i;
|
LL | return i;
|
||||||
| -------- if the loop doesn't execute, this value would never get returned
|
| -------- if the loop doesn't execute, this value would never get returned
|
||||||
LL | } else if n < 10 {
|
LL | } else if n < 10 {
|
||||||
@ -27,7 +27,32 @@ LL | } else if n < 20 {
|
|||||||
LL | return 2;
|
LL | return 2;
|
||||||
| -------- if the loop doesn't execute, this value would never get returned
|
| -------- if the loop doesn't execute, this value would never get returned
|
||||||
= note: if the loop doesn't execute, 3 other values would never get returned
|
= note: if the loop doesn't execute, 3 other values would never get returned
|
||||||
= 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
|
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(n: i32) -> Option<i32> {
|
||||||
|
LL | for i in 0..0 {
|
||||||
|
LL | if n < 0 {
|
||||||
|
LL ~ return Some(i);
|
||||||
|
LL | } else if n < 10 {
|
||||||
|
LL ~ return Some(1);
|
||||||
|
LL | } else if n < 20 {
|
||||||
|
LL ~ return Some(2);
|
||||||
|
LL | } else if n < 30 {
|
||||||
|
LL ~ return Some(3);
|
||||||
|
LL | } else if n < 40 {
|
||||||
|
LL ~ return Some(4);
|
||||||
|
LL | } else {
|
||||||
|
LL ~ return Some(5);
|
||||||
|
LL | }
|
||||||
|
LL |
|
||||||
|
LL ~ }
|
||||||
|
LL ~ None
|
||||||
|
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
fn foo() -> i32 {
|
fn foo() -> i32 { //~ HELP otherwise consider changing the return type to account for that possibility
|
||||||
for i in 0..0 {
|
for i in 0..0 { //~ ERROR mismatched types [E0308]
|
||||||
//~^ ERROR: mismatched types [E0308]
|
|
||||||
return i;
|
return i;
|
||||||
}
|
} //~ HELP return a value for the case when the loop has zero elements to iterate on
|
||||||
//~| 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -4,7 +4,6 @@ error[E0308]: mismatched types
|
|||||||
LL | fn foo() -> i32 {
|
LL | fn foo() -> i32 {
|
||||||
| --- expected `i32` because of return type
|
| --- expected `i32` because of return type
|
||||||
LL | / for i in 0..0 {
|
LL | / for i in 0..0 {
|
||||||
LL | |
|
|
||||||
LL | | return i;
|
LL | | return i;
|
||||||
LL | | }
|
LL | | }
|
||||||
| |_____^ expected `i32`, found `()`
|
| |_____^ expected `i32`, found `()`
|
||||||
@ -14,10 +13,21 @@ note: the function expects a value to always be returned, but loops might run ze
|
|||||||
|
|
|
|
||||||
LL | for i in 0..0 {
|
LL | for i in 0..0 {
|
||||||
| ^^^^^^^^^^^^^ this might have zero elements to iterate on
|
| ^^^^^^^^^^^^^ this might have zero elements to iterate on
|
||||||
LL |
|
|
||||||
LL | return i;
|
LL | return i;
|
||||||
| -------- if the loop doesn't execute, this value would never get returned
|
| -------- 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, or consider changing the return type to account for that possibility
|
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
|
||||||
|
|
|
||||||
|
|
||||||
error: aborting due to 1 previous error
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user