Skip to content

Commit 37d2ea2

Browse files
committed
Properly handle async blocks and fns in if exprs without else
When encountering a tail expression in the then arm of an `if` expression without an `else` arm, account for `async fn` and `async` blocks to suggest `return`ing the value and pointing at the return type of the `async fn`. We now also account for AFIT when looking for the return type to point at. Fix #115405.
1 parent bdc1592 commit 37d2ea2

File tree

12 files changed

+236
-47
lines changed

12 files changed

+236
-47
lines changed

compiler/rustc_hir_typeck/src/coercion.rs

+30-9
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,17 @@ impl<'a, 'tcx> Deref for Coerce<'a, 'tcx> {
9292

9393
type CoerceResult<'tcx> = InferResult<'tcx, (Vec<Adjustment<'tcx>>, Ty<'tcx>)>;
9494

95-
struct CollectRetsVisitor<'tcx> {
96-
ret_exprs: Vec<&'tcx hir::Expr<'tcx>>,
95+
pub struct CollectRetsVisitor<'tcx> {
96+
pub ret_exprs: Vec<&'tcx hir::Expr<'tcx>>,
9797
}
9898

9999
impl<'tcx> Visitor<'tcx> for CollectRetsVisitor<'tcx> {
100100
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
101-
if let hir::ExprKind::Ret(_) = expr.kind {
102-
self.ret_exprs.push(expr);
101+
match expr.kind {
102+
hir::ExprKind::Ret(_) => self.ret_exprs.push(expr),
103+
// `return` in closures does not return from the outer function
104+
hir::ExprKind::Closure(_) => return,
105+
_ => {}
103106
}
104107
intravisit::walk_expr(self, expr);
105108
}
@@ -1845,13 +1848,31 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
18451848
}
18461849

18471850
let parent_id = fcx.tcx.hir().get_parent_item(id);
1848-
let parent_item = fcx.tcx.hir_node_by_def_id(parent_id.def_id);
1851+
let mut parent_item = fcx.tcx.hir_node_by_def_id(parent_id.def_id);
1852+
// When suggesting return, we need to account for closures and async blocks, not just items.
1853+
for (_, node) in fcx.tcx.hir().parent_iter(id) {
1854+
match node {
1855+
hir::Node::Expr(&hir::Expr {
1856+
kind: hir::ExprKind::Closure(hir::Closure { .. }),
1857+
..
1858+
}) => {
1859+
parent_item = node;
1860+
break;
1861+
}
1862+
hir::Node::Item(_) | hir::Node::TraitItem(_) | hir::Node::ImplItem(_) => break,
1863+
_ => {}
1864+
}
1865+
}
18491866

1850-
if let (Some(expr), Some(_), Some((fn_id, fn_decl, _, _))) =
1851-
(expression, blk_id, fcx.get_node_fn_decl(parent_item))
1852-
{
1867+
if let (Some(expr), Some(_), Some(fn_decl)) = (expression, blk_id, parent_item.fn_decl()) {
18531868
fcx.suggest_missing_break_or_return_expr(
1854-
&mut err, expr, fn_decl, expected, found, id, fn_id,
1869+
&mut err,
1870+
expr,
1871+
fn_decl,
1872+
expected,
1873+
found,
1874+
id,
1875+
parent_id.into(),
18551876
);
18561877
}
18571878

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+29-8
Original file line numberDiff line numberDiff line change
@@ -963,14 +963,35 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
963963
owner_id,
964964
..
965965
}) => Some((hir::HirId::make_owner(owner_id.def_id), &sig.decl, ident, false)),
966-
Node::Expr(&hir::Expr { hir_id, kind: hir::ExprKind::Closure(..), .. })
967-
if let Node::Item(&hir::Item {
968-
ident,
969-
kind: hir::ItemKind::Fn(ref sig, ..),
970-
owner_id,
971-
..
972-
}) = self.tcx.parent_hir_node(hir_id) =>
973-
{
966+
Node::Expr(&hir::Expr {
967+
hir_id,
968+
kind:
969+
hir::ExprKind::Closure(hir::Closure {
970+
kind: hir::ClosureKind::Coroutine(..), ..
971+
}),
972+
..
973+
}) => {
974+
let (ident, sig, owner_id) = match self.tcx.parent_hir_node(hir_id) {
975+
Node::Item(&hir::Item {
976+
ident,
977+
kind: hir::ItemKind::Fn(ref sig, ..),
978+
owner_id,
979+
..
980+
}) => (ident, sig, owner_id),
981+
Node::TraitItem(&hir::TraitItem {
982+
ident,
983+
kind: hir::TraitItemKind::Fn(ref sig, ..),
984+
owner_id,
985+
..
986+
}) => (ident, sig, owner_id),
987+
Node::ImplItem(&hir::ImplItem {
988+
ident,
989+
kind: hir::ImplItemKind::Fn(ref sig, ..),
990+
owner_id,
991+
..
992+
}) => (ident, sig, owner_id),
993+
_ => return None,
994+
};
974995
Some((
975996
hir::HirId::make_owner(owner_id.def_id),
976997
&sig.decl,

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1726,7 +1726,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17261726
}
17271727

17281728
/// Given a function block's `HirId`, returns its `FnDecl` if it exists, or `None` otherwise.
1729-
fn get_parent_fn_decl(&self, blk_id: hir::HirId) -> Option<(&'tcx hir::FnDecl<'tcx>, Ident)> {
1729+
pub(crate) fn get_parent_fn_decl(
1730+
&self,
1731+
blk_id: hir::HirId,
1732+
) -> Option<(&'tcx hir::FnDecl<'tcx>, Ident)> {
17301733
let parent = self.tcx.hir_node_by_def_id(self.tcx.hir().get_parent_item(blk_id).def_id);
17311734
self.get_node_fn_decl(parent).map(|(_, fn_decl, ident, _)| (fn_decl, ident))
17321735
}

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+75-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::FnCtxt;
22

3+
use crate::coercion::CollectRetsVisitor;
34
use crate::errors;
45
use crate::fluent_generated as fluent;
56
use crate::fn_ctxt::rustc_span::BytePos;
@@ -16,6 +17,7 @@ use rustc_errors::{Applicability, Diagnostic, MultiSpan};
1617
use rustc_hir as hir;
1718
use rustc_hir::def::Res;
1819
use rustc_hir::def::{CtorKind, CtorOf, DefKind};
20+
use rustc_hir::intravisit::{Map, Visitor};
1921
use rustc_hir::lang_items::LangItem;
2022
use rustc_hir::{
2123
CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, GenericBound, HirId, Node,
@@ -827,6 +829,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
827829
}
828830
hir::FnRetTy::Return(hir_ty) => {
829831
if let hir::TyKind::OpaqueDef(item_id, ..) = hir_ty.kind
832+
// FIXME: account for RPITIT.
830833
&& let hir::Node::Item(hir::Item {
831834
kind: hir::ItemKind::OpaqueTy(op_ty), ..
832835
}) = self.tcx.hir_node(item_id.hir_id())
@@ -1038,33 +1041,81 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10381041
return;
10391042
}
10401043

1041-
if let hir::FnRetTy::Return(ty) = fn_decl.output {
1042-
let ty = self.astconv().ast_ty_to_ty(ty);
1043-
let bound_vars = self.tcx.late_bound_vars(fn_id);
1044-
let ty = self
1045-
.tcx
1046-
.instantiate_bound_regions_with_erased(Binder::bind_with_vars(ty, bound_vars));
1047-
let ty = match self.tcx.asyncness(fn_id.owner) {
1048-
ty::Asyncness::Yes => self.get_impl_future_output_ty(ty).unwrap_or_else(|| {
1049-
span_bug!(fn_decl.output.span(), "failed to get output type of async function")
1050-
}),
1051-
ty::Asyncness::No => ty,
1052-
};
1053-
let ty = self.normalize(expr.span, ty);
1054-
if self.can_coerce(found, ty) {
1055-
if let Some(owner_node) = self.tcx.hir_node(fn_id).as_owner()
1056-
&& let Some(span) = expr.span.find_ancestor_inside(*owner_node.span())
1044+
let in_closure = matches!(
1045+
self.tcx
1046+
.hir()
1047+
.parent_iter(id)
1048+
.filter(|(_, node)| {
1049+
matches!(
1050+
node,
1051+
Node::Expr(Expr { kind: ExprKind::Closure(..), .. })
1052+
| Node::Item(_)
1053+
| Node::TraitItem(_)
1054+
| Node::ImplItem(_)
1055+
)
1056+
})
1057+
.next(),
1058+
Some((_, Node::Expr(Expr { kind: ExprKind::Closure(..), .. })))
1059+
);
1060+
1061+
let can_return = match fn_decl.output {
1062+
hir::FnRetTy::Return(ty) => {
1063+
let ty = self.astconv().ast_ty_to_ty(ty);
1064+
let bound_vars = self.tcx.late_bound_vars(fn_id);
1065+
let ty = self
1066+
.tcx
1067+
.instantiate_bound_regions_with_erased(Binder::bind_with_vars(ty, bound_vars));
1068+
let ty = match self.tcx.asyncness(fn_id.owner) {
1069+
ty::Asyncness::Yes => self.get_impl_future_output_ty(ty).unwrap_or_else(|| {
1070+
span_bug!(
1071+
fn_decl.output.span(),
1072+
"failed to get output type of async function"
1073+
)
1074+
}),
1075+
ty::Asyncness::No => ty,
1076+
};
1077+
let ty = self.normalize(expr.span, ty);
1078+
self.can_coerce(found, ty)
1079+
}
1080+
hir::FnRetTy::DefaultReturn(_) if in_closure => {
1081+
let mut rets = vec![];
1082+
if let Some(ret_coercion) = self.ret_coercion.as_ref() {
1083+
let ret_ty = ret_coercion.borrow().expected_ty();
1084+
rets.push(ret_ty);
1085+
}
1086+
let mut visitor = CollectRetsVisitor { ret_exprs: vec![] };
1087+
if let Some(item) = self.tcx.hir().find(id)
1088+
&& let Node::Expr(expr) = item
10571089
{
1058-
err.multipart_suggestion(
1059-
"you might have meant to return this value",
1060-
vec![
1061-
(span.shrink_to_lo(), "return ".to_string()),
1062-
(span.shrink_to_hi(), ";".to_string()),
1063-
],
1064-
Applicability::MaybeIncorrect,
1065-
);
1090+
visitor.visit_expr(expr);
1091+
for expr in visitor.ret_exprs {
1092+
if let Some(ty) = self.typeck_results.borrow().node_type_opt(expr.hir_id) {
1093+
rets.push(ty);
1094+
}
1095+
}
1096+
if let hir::ExprKind::Block(hir::Block { expr: Some(expr), .. }, _) = expr.kind
1097+
{
1098+
if let Some(ty) = self.typeck_results.borrow().node_type_opt(expr.hir_id) {
1099+
rets.push(ty);
1100+
}
1101+
}
10661102
}
1103+
rets.into_iter().all(|ty| self.can_coerce(found, ty))
10671104
}
1105+
_ => false,
1106+
};
1107+
if can_return
1108+
&& let Some(owner_node) = self.tcx.hir_node(fn_id).as_owner()
1109+
&& let Some(span) = expr.span.find_ancestor_inside(owner_node.span())
1110+
{
1111+
err.multipart_suggestion(
1112+
"you might have meant to return this value",
1113+
vec![
1114+
(span.shrink_to_lo(), "return ".to_string()),
1115+
(span.shrink_to_hi(), ";".to_string()),
1116+
],
1117+
Applicability::MaybeIncorrect,
1118+
);
10681119
}
10691120
}
10701121

compiler/rustc_middle/src/hir/map/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ impl<'hir> Map<'hir> {
617617
Node::Item(_)
618618
| Node::ForeignItem(_)
619619
| Node::TraitItem(_)
620-
| Node::Expr(Expr { kind: ExprKind::Closure { .. }, .. })
620+
| Node::Expr(Expr { kind: ExprKind::Closure(_), .. })
621621
| Node::ImplItem(_)
622622
// The input node `id` must be enclosed in the method's body as opposed
623623
// to some other place such as its return type (fixes #114918).

compiler/rustc_parse/src/parser/diagnostics.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ impl<'a> Parser<'a> {
900900
// fn foo() -> Foo {
901901
// field: value,
902902
// }
903-
info!(?maybe_struct_name, ?self.token);
903+
debug!(?maybe_struct_name, ?self.token);
904904
let mut snapshot = self.create_snapshot_for_diagnostic();
905905
let path = Path {
906906
segments: ThinVec::new(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-rustfix
2+
// edition:2021
3+
use std::future::Future;
4+
use std::pin::Pin;
5+
pub struct S;
6+
pub fn foo() {
7+
let _ = Box::pin(async move {
8+
if true {
9+
return Ok(S); //~ ERROR mismatched types
10+
}
11+
Err(())
12+
});
13+
}
14+
pub fn bar() -> Pin<Box<dyn Future<Output = Result<S, ()>> + 'static>> {
15+
Box::pin(async move {
16+
if true {
17+
return Ok(S); //~ ERROR mismatched types
18+
}
19+
Err(())
20+
})
21+
}
22+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-rustfix
2+
// edition:2021
3+
use std::future::Future;
4+
use std::pin::Pin;
5+
pub struct S;
6+
pub fn foo() {
7+
let _ = Box::pin(async move {
8+
if true {
9+
Ok(S) //~ ERROR mismatched types
10+
}
11+
Err(())
12+
});
13+
}
14+
pub fn bar() -> Pin<Box<dyn Future<Output = Result<S, ()>> + 'static>> {
15+
Box::pin(async move {
16+
if true {
17+
Ok(S) //~ ERROR mismatched types
18+
}
19+
Err(())
20+
})
21+
}
22+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/missing-return-in-async-block.rs:9:13
3+
|
4+
LL | / if true {
5+
LL | | Ok(S)
6+
| | ^^^^^ expected `()`, found `Result<S, _>`
7+
LL | | }
8+
| |_________- expected this to be `()`
9+
|
10+
= note: expected unit type `()`
11+
found enum `Result<S, _>`
12+
help: you might have meant to return this value
13+
|
14+
LL | return Ok(S);
15+
| ++++++ +
16+
17+
error[E0308]: mismatched types
18+
--> $DIR/missing-return-in-async-block.rs:17:13
19+
|
20+
LL | / if true {
21+
LL | | Ok(S)
22+
| | ^^^^^ expected `()`, found `Result<S, _>`
23+
LL | | }
24+
| |_________- expected this to be `()`
25+
|
26+
= note: expected unit type `()`
27+
found enum `Result<S, _>`
28+
help: you might have meant to return this value
29+
|
30+
LL | return Ok(S);
31+
| ++++++ +
32+
33+
error: aborting due to 2 previous errors
34+
35+
For more information about this error, try `rustc --explain E0308`.

tests/ui/impl-trait/in-trait/default-body-type-err-2.stderr

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
error[E0308]: mismatched types
22
--> $DIR/default-body-type-err-2.rs:7:9
33
|
4+
LL | async fn woopsie_async(&self) -> String {
5+
| ------ expected `String` because of return type
46
LL | 42
57
| ^^- help: try using a conversion method: `.to_string()`
68
| |

tests/ui/loops/dont-suggest-break-thru-item.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ fn closure() {
88
if true {
99
Err(1)
1010
//~^ ERROR mismatched types
11+
//~| HELP you might have meant to return this value
1112
}
1213

1314
Ok(())
@@ -21,6 +22,7 @@ fn async_block() {
2122
if true {
2223
Err(1)
2324
//~^ ERROR mismatched types
25+
//~| HELP you might have meant to return this value
2426
}
2527

2628
Ok(())

0 commit comments

Comments
 (0)