Skip to content

Commit 97c9f16

Browse files
authored
Rollup merge of #98784 - compiler-errors:forgot-to-return-binding, r=estebank
Suggest returning local on "expected `ty`, found `()`" due to expr-less block Putting this up for _initial_ review. Notably, this doesn't consider if the value has possibly been moved, or whether the type is `Copy`. It also provides a structured suggestion if there's one "preferred" binding that matches the type (i.e. one binding in the block or its parent), otherwise it just points them out if there's fewer than 4 of them. Fixes #98177 r? `@estebank`
2 parents f426146 + b0a8190 commit 97c9f16

File tree

10 files changed

+314
-52
lines changed

10 files changed

+314
-52
lines changed

compiler/rustc_typeck/src/check/fn_ctxt/checks.rs

+4-40
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ use rustc_middle::ty::{self, DefIdTree, IsSuggestable, Ty};
3131
use rustc_session::Session;
3232
use rustc_span::symbol::Ident;
3333
use rustc_span::{self, Span};
34-
use rustc_trait_selection::traits::{
35-
self, ObligationCauseCode, SelectionContext, StatementAsExpression,
36-
};
34+
use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext};
3735

3836
use std::iter;
3937
use std::slice;
@@ -1410,7 +1408,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14101408
&self.misc(sp),
14111409
&mut |err| {
14121410
if let Some(expected_ty) = expected.only_has_type(self) {
1413-
self.consider_hint_about_removing_semicolon(blk, expected_ty, err);
1411+
if !self.consider_removing_semicolon(blk, expected_ty, err) {
1412+
self.consider_returning_binding(blk, expected_ty, err);
1413+
}
14141414
if expected_ty == self.tcx.types.bool {
14151415
// If this is caused by a missing `let` in a `while let`,
14161416
// silence this redundant error, as we already emit E0070.
@@ -1478,42 +1478,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14781478
ty
14791479
}
14801480

1481-
/// A common error is to add an extra semicolon:
1482-
///
1483-
/// ```compile_fail,E0308
1484-
/// fn foo() -> usize {
1485-
/// 22;
1486-
/// }
1487-
/// ```
1488-
///
1489-
/// This routine checks if the final statement in a block is an
1490-
/// expression with an explicit semicolon whose type is compatible
1491-
/// with `expected_ty`. If so, it suggests removing the semicolon.
1492-
fn consider_hint_about_removing_semicolon(
1493-
&self,
1494-
blk: &'tcx hir::Block<'tcx>,
1495-
expected_ty: Ty<'tcx>,
1496-
err: &mut Diagnostic,
1497-
) {
1498-
if let Some((span_semi, boxed)) = self.could_remove_semicolon(blk, expected_ty) {
1499-
if let StatementAsExpression::NeedsBoxing = boxed {
1500-
err.span_suggestion_verbose(
1501-
span_semi,
1502-
"consider removing this semicolon and boxing the expression",
1503-
"",
1504-
Applicability::HasPlaceholders,
1505-
);
1506-
} else {
1507-
err.span_suggestion_short(
1508-
span_semi,
1509-
"remove this semicolon",
1510-
"",
1511-
Applicability::MachineApplicable,
1512-
);
1513-
}
1514-
}
1515-
}
1516-
15171481
fn parent_item_span(&self, id: hir::HirId) -> Option<Span> {
15181482
let node = self.tcx.hir().get_by_def_id(self.tcx.hir().get_parent_item(id));
15191483
match node {

compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs

+156-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::astconv::AstConv;
33
use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel};
44

55
use rustc_ast::util::parser::ExprPrecedence;
6+
use rustc_data_structures::stable_set::FxHashSet;
67
use rustc_errors::{Applicability, Diagnostic, MultiSpan};
78
use rustc_hir as hir;
89
use rustc_hir::def::{CtorOf, DefKind};
@@ -11,12 +12,12 @@ use rustc_hir::{
1112
Expr, ExprKind, GenericBound, Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicate,
1213
};
1314
use rustc_infer::infer::{self, TyCtxtInferExt};
14-
use rustc_infer::traits;
15+
use rustc_infer::traits::{self, StatementAsExpression};
1516
use rustc_middle::lint::in_external_macro;
16-
use rustc_middle::ty::{self, Binder, IsSuggestable, Subst, ToPredicate, Ty};
17+
use rustc_middle::ty::{self, Binder, IsSuggestable, Subst, ToPredicate, Ty, TypeVisitable};
1718
use rustc_span::symbol::sym;
1819
use rustc_span::Span;
19-
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
20+
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
2021

2122
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
2223
pub(in super::super) fn suggest_semicolon_at_end(&self, span: Span, err: &mut Diagnostic) {
@@ -864,4 +865,156 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
864865
);
865866
}
866867
}
868+
869+
/// A common error is to add an extra semicolon:
870+
///
871+
/// ```compile_fail,E0308
872+
/// fn foo() -> usize {
873+
/// 22;
874+
/// }
875+
/// ```
876+
///
877+
/// This routine checks if the final statement in a block is an
878+
/// expression with an explicit semicolon whose type is compatible
879+
/// with `expected_ty`. If so, it suggests removing the semicolon.
880+
pub(crate) fn consider_removing_semicolon(
881+
&self,
882+
blk: &'tcx hir::Block<'tcx>,
883+
expected_ty: Ty<'tcx>,
884+
err: &mut Diagnostic,
885+
) -> bool {
886+
if let Some((span_semi, boxed)) = self.could_remove_semicolon(blk, expected_ty) {
887+
if let StatementAsExpression::NeedsBoxing = boxed {
888+
err.span_suggestion_verbose(
889+
span_semi,
890+
"consider removing this semicolon and boxing the expression",
891+
"",
892+
Applicability::HasPlaceholders,
893+
);
894+
} else {
895+
err.span_suggestion_short(
896+
span_semi,
897+
"remove this semicolon",
898+
"",
899+
Applicability::MachineApplicable,
900+
);
901+
}
902+
true
903+
} else {
904+
false
905+
}
906+
}
907+
908+
pub(crate) fn consider_returning_binding(
909+
&self,
910+
blk: &'tcx hir::Block<'tcx>,
911+
expected_ty: Ty<'tcx>,
912+
err: &mut Diagnostic,
913+
) {
914+
let mut shadowed = FxHashSet::default();
915+
let mut candidate_idents = vec![];
916+
let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
917+
if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
918+
&& let Some(pat_ty) = self.typeck_results.borrow().node_type_opt(*hir_id)
919+
{
920+
let pat_ty = self.resolve_vars_if_possible(pat_ty);
921+
if self.can_coerce(pat_ty, expected_ty)
922+
&& !(pat_ty, expected_ty).references_error()
923+
&& shadowed.insert(ident.name)
924+
{
925+
candidate_idents.push((*ident, pat_ty));
926+
}
927+
}
928+
true
929+
};
930+
931+
let hir = self.tcx.hir();
932+
for stmt in blk.stmts.iter().rev() {
933+
let StmtKind::Local(local) = &stmt.kind else { continue; };
934+
local.pat.walk(&mut find_compatible_candidates);
935+
}
936+
match hir.find(hir.get_parent_node(blk.hir_id)) {
937+
Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => {
938+
match hir.find(hir.get_parent_node(*hir_id)) {
939+
Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
940+
pat.walk(&mut find_compatible_candidates);
941+
}
942+
Some(
943+
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
944+
| hir::Node::ImplItem(hir::ImplItem {
945+
kind: hir::ImplItemKind::Fn(_, body),
946+
..
947+
})
948+
| hir::Node::TraitItem(hir::TraitItem {
949+
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
950+
..
951+
})
952+
| hir::Node::Expr(hir::Expr {
953+
kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
954+
..
955+
}),
956+
) => {
957+
for param in hir.body(*body).params {
958+
param.pat.walk(&mut find_compatible_candidates);
959+
}
960+
}
961+
Some(hir::Node::Expr(hir::Expr {
962+
kind:
963+
hir::ExprKind::If(
964+
hir::Expr { kind: hir::ExprKind::Let(let_), .. },
965+
then_block,
966+
_,
967+
),
968+
..
969+
})) if then_block.hir_id == *hir_id => {
970+
let_.pat.walk(&mut find_compatible_candidates);
971+
}
972+
_ => {}
973+
}
974+
}
975+
_ => {}
976+
}
977+
978+
match &candidate_idents[..] {
979+
[(ident, _ty)] => {
980+
let sm = self.tcx.sess.source_map();
981+
if let Some(stmt) = blk.stmts.last() {
982+
let stmt_span = sm.stmt_span(stmt.span, blk.span);
983+
let sugg = if sm.is_multiline(blk.span)
984+
&& let Some(spacing) = sm.indentation_before(stmt_span)
985+
{
986+
format!("\n{spacing}{ident}")
987+
} else {
988+
format!(" {ident}")
989+
};
990+
err.span_suggestion_verbose(
991+
stmt_span.shrink_to_hi(),
992+
format!("consider returning the local binding `{ident}`"),
993+
sugg,
994+
Applicability::MachineApplicable,
995+
);
996+
} else {
997+
let sugg = if sm.is_multiline(blk.span)
998+
&& let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
999+
{
1000+
format!("\n{spacing} {ident}\n{spacing}")
1001+
} else {
1002+
format!(" {ident} ")
1003+
};
1004+
let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
1005+
err.span_suggestion_verbose(
1006+
sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
1007+
format!("consider returning the local binding `{ident}`"),
1008+
sugg,
1009+
Applicability::MachineApplicable,
1010+
);
1011+
}
1012+
}
1013+
values if (1..3).contains(&values.len()) => {
1014+
let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
1015+
err.span_note(spans, "consider returning one of these bindings");
1016+
}
1017+
_ => {}
1018+
}
1019+
}
8671020
}

src/test/ui/async-await/async-block-control-flow-static-semantics.stderr

+9-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ LL | | break 0u8;
1818
LL | | };
1919
| |_________- enclosing `async` block
2020

21+
error[E0271]: type mismatch resolving `<impl Future<Output = u8> as Future>::Output == ()`
22+
--> $DIR/async-block-control-flow-static-semantics.rs:26:39
23+
|
24+
LL | let _: &dyn Future<Output = ()> = &block;
25+
| ^^^^^^ expected `()`, found `u8`
26+
|
27+
= note: required for the cast from `impl Future<Output = u8>` to the object type `dyn Future<Output = ()>`
28+
2129
error[E0308]: mismatched types
2230
--> $DIR/async-block-control-flow-static-semantics.rs:21:58
2331
|
@@ -32,7 +40,7 @@ LL | | }
3240
| |_^ expected `u8`, found `()`
3341

3442
error[E0271]: type mismatch resolving `<impl Future<Output = u8> as Future>::Output == ()`
35-
--> $DIR/async-block-control-flow-static-semantics.rs:26:39
43+
--> $DIR/async-block-control-flow-static-semantics.rs:17:39
3644
|
3745
LL | let _: &dyn Future<Output = ()> = &block;
3846
| ^^^^^^ expected `()`, found `u8`
@@ -47,14 +55,6 @@ LL | fn return_targets_async_block_not_fn() -> u8 {
4755
| |
4856
| implicitly returns `()` as its body has no tail or `return` expression
4957

50-
error[E0271]: type mismatch resolving `<impl Future<Output = u8> as Future>::Output == ()`
51-
--> $DIR/async-block-control-flow-static-semantics.rs:17:39
52-
|
53-
LL | let _: &dyn Future<Output = ()> = &block;
54-
| ^^^^^^ expected `()`, found `u8`
55-
|
56-
= note: required for the cast from `impl Future<Output = u8>` to the object type `dyn Future<Output = ()>`
57-
5858
error[E0308]: mismatched types
5959
--> $DIR/async-block-control-flow-static-semantics.rs:47:44
6060
|

src/test/ui/liveness/liveness-forgot-ret.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ LL | fn f(a: isize) -> isize { if god_exists(a) { return 5; }; }
55
| - ^^^^^ expected `isize`, found `()`
66
| |
77
| implicitly returns `()` as its body has no tail or `return` expression
8+
|
9+
help: consider returning the local binding `a`
10+
|
11+
LL | fn f(a: isize) -> isize { if god_exists(a) { return 5; }; a }
12+
| +
813

914
error: aborting due to previous error
1015

src/test/ui/parser/issues/issue-33413.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ LL | fn f(*, a: u8) -> u8 {}
1111
| - ^^ expected `u8`, found `()`
1212
| |
1313
| implicitly returns `()` as its body has no tail or `return` expression
14+
|
15+
help: consider returning the local binding `a`
16+
|
17+
LL | fn f(*, a: u8) -> u8 { a }
18+
| +
1419

1520
error: aborting due to 2 previous errors
1621

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fn a(i: i32) -> i32 {
2+
//~^ ERROR mismatched types
3+
let j = 2i32;
4+
}
5+
6+
fn b(i: i32, j: i32) -> i32 {}
7+
//~^ ERROR mismatched types
8+
9+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/return-bindings-multi.rs:1:17
3+
|
4+
LL | fn a(i: i32) -> i32 {
5+
| - ^^^ expected `i32`, found `()`
6+
| |
7+
| implicitly returns `()` as its body has no tail or `return` expression
8+
|
9+
note: consider returning one of these bindings
10+
--> $DIR/return-bindings-multi.rs:1:6
11+
|
12+
LL | fn a(i: i32) -> i32 {
13+
| ^
14+
LL |
15+
LL | let j = 2i32;
16+
| ^
17+
18+
error[E0308]: mismatched types
19+
--> $DIR/return-bindings-multi.rs:6:25
20+
|
21+
LL | fn b(i: i32, j: i32) -> i32 {}
22+
| - ^^^ expected `i32`, found `()`
23+
| |
24+
| implicitly returns `()` as its body has no tail or `return` expression
25+
|
26+
note: consider returning one of these bindings
27+
--> $DIR/return-bindings-multi.rs:6:6
28+
|
29+
LL | fn b(i: i32, j: i32) -> i32 {}
30+
| ^ ^
31+
32+
error: aborting due to 2 previous errors
33+
34+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-rustfix
2+
3+
#![allow(unused)]
4+
5+
fn a(i: i32) -> i32 { i }
6+
//~^ ERROR mismatched types
7+
8+
fn b(opt_str: Option<String>) {
9+
let s: String = if let Some(s) = opt_str {
10+
s
11+
//~^ ERROR mismatched types
12+
} else {
13+
String::new()
14+
};
15+
}
16+
17+
fn c() -> Option<i32> {
18+
//~^ ERROR mismatched types
19+
let x = Some(1);
20+
x
21+
}
22+
23+
fn main() {}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// run-rustfix
2+
3+
#![allow(unused)]
4+
5+
fn a(i: i32) -> i32 {}
6+
//~^ ERROR mismatched types
7+
8+
fn b(opt_str: Option<String>) {
9+
let s: String = if let Some(s) = opt_str {
10+
//~^ ERROR mismatched types
11+
} else {
12+
String::new()
13+
};
14+
}
15+
16+
fn c() -> Option<i32> {
17+
//~^ ERROR mismatched types
18+
let x = Some(1);
19+
}
20+
21+
fn main() {}

0 commit comments

Comments
 (0)