Skip to content

Commit a2112fc

Browse files
committed
Auto merge of #106519 - estebank:tail-unit, r=cjgillot
Detect bindings assigned blocks without tail expressions Fix #44173.
2 parents d72b7d2 + 031e085 commit a2112fc

File tree

6 files changed

+285
-23
lines changed

6 files changed

+285
-23
lines changed

compiler/rustc_hir_typeck/src/_match.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
224224
let mut ret_span: MultiSpan = semi_span.into();
225225
ret_span.push_span_label(
226226
expr.span,
227-
"this could be implicitly returned but it is a statement, not a \
228-
tail expression",
227+
"this could be implicitly returned but it is a statement, not a tail expression",
229228
);
230229
ret_span.push_span_label(ret, "the `match` arms can conform to this return type");
231230
ret_span.push_span_label(
232231
semi_span,
233-
"the `match` is a statement because of this semicolon, consider \
234-
removing it",
232+
"the `match` is a statement because of this semicolon, consider removing it",
235233
);
236234
diag.span_note(ret_span, "you might have meant to return the `match` expression");
237235
diag.tool_only_span_suggestion(

compiler/rustc_hir_typeck/src/demand.rs

+45
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8383
self.note_need_for_fn_pointer(err, expected, expr_ty);
8484
self.note_internal_mutation_in_method(err, expr, expected, expr_ty);
8585
self.check_for_range_as_method_call(err, expr, expr_ty, expected);
86+
self.check_for_binding_assigned_block_without_tail_expression(err, expr, expr_ty, expected);
8687
}
8788

8889
/// Requires that the two types unify, and prints an error message if
@@ -1887,4 +1888,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18871888
Applicability::MachineApplicable,
18881889
);
18891890
}
1891+
1892+
/// Identify when the type error is because `()` is found in a binding that was assigned a
1893+
/// block without a tail expression.
1894+
fn check_for_binding_assigned_block_without_tail_expression(
1895+
&self,
1896+
err: &mut Diagnostic,
1897+
expr: &hir::Expr<'_>,
1898+
checked_ty: Ty<'tcx>,
1899+
expected_ty: Ty<'tcx>,
1900+
) {
1901+
if !checked_ty.is_unit() {
1902+
return;
1903+
}
1904+
let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { return; };
1905+
let hir::def::Res::Local(hir_id) = path.res else { return; };
1906+
let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else {
1907+
return;
1908+
};
1909+
let Some(hir::Node::Local(hir::Local {
1910+
ty: None,
1911+
init: Some(init),
1912+
..
1913+
})) = self.tcx.hir().find_parent(pat.hir_id) else { return; };
1914+
let hir::ExprKind::Block(block, None) = init.kind else { return; };
1915+
if block.expr.is_some() {
1916+
return;
1917+
}
1918+
let [.., stmt] = block.stmts else {
1919+
err.span_label(block.span, "this empty block is missing a tail expression");
1920+
return;
1921+
};
1922+
let hir::StmtKind::Semi(tail_expr) = stmt.kind else { return; };
1923+
let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else { return; };
1924+
if self.can_eq(self.param_env, expected_ty, ty).is_ok() {
1925+
err.span_suggestion_short(
1926+
stmt.span.with_lo(tail_expr.span.hi()),
1927+
"remove this semicolon",
1928+
"",
1929+
Applicability::MachineApplicable,
1930+
);
1931+
} else {
1932+
err.span_label(block.span, "this block is missing a tail expression");
1933+
}
1934+
}
18901935
}

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+36-18
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
771771
),
772772
}
773773
};
774-
774+
self.check_for_binding_assigned_block_without_tail_expression(
775+
&obligation,
776+
&mut err,
777+
trait_predicate,
778+
);
775779
if self.suggest_add_reference_to_arg(
776780
&obligation,
777781
&mut err,
@@ -2266,23 +2270,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
22662270
if let (Some(body_id), Some(ty::subst::GenericArgKind::Type(_))) =
22672271
(body_id, subst.map(|subst| subst.unpack()))
22682272
{
2269-
struct FindExprBySpan<'hir> {
2270-
span: Span,
2271-
result: Option<&'hir hir::Expr<'hir>>,
2272-
}
2273-
2274-
impl<'v> hir::intravisit::Visitor<'v> for FindExprBySpan<'v> {
2275-
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
2276-
if self.span == ex.span {
2277-
self.result = Some(ex);
2278-
} else {
2279-
hir::intravisit::walk_expr(self, ex);
2280-
}
2281-
}
2282-
}
2283-
2284-
let mut expr_finder = FindExprBySpan { span, result: None };
2285-
2273+
let mut expr_finder = FindExprBySpan::new(span);
22862274
expr_finder.visit_expr(&self.tcx.hir().body(body_id).value);
22872275

22882276
if let Some(hir::Expr {
@@ -2769,6 +2757,36 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
27692757
}
27702758
}
27712759

2760+
/// Crude way of getting back an `Expr` from a `Span`.
2761+
pub struct FindExprBySpan<'hir> {
2762+
pub span: Span,
2763+
pub result: Option<&'hir hir::Expr<'hir>>,
2764+
pub ty_result: Option<&'hir hir::Ty<'hir>>,
2765+
}
2766+
2767+
impl<'hir> FindExprBySpan<'hir> {
2768+
fn new(span: Span) -> Self {
2769+
Self { span, result: None, ty_result: None }
2770+
}
2771+
}
2772+
2773+
impl<'v> Visitor<'v> for FindExprBySpan<'v> {
2774+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
2775+
if self.span == ex.span {
2776+
self.result = Some(ex);
2777+
} else {
2778+
hir::intravisit::walk_expr(self, ex);
2779+
}
2780+
}
2781+
fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
2782+
if self.span == ty.span {
2783+
self.ty_result = Some(ty);
2784+
} else {
2785+
hir::intravisit::walk_ty(self, ty);
2786+
}
2787+
}
2788+
}
2789+
27722790
/// Look for type `param` in an ADT being used only through a reference to confirm that suggesting
27732791
/// `param: ?Sized` would be a valid constraint.
27742792
struct FindTypeParam {

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+71-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// ignore-tidy-filelength
22

3-
use super::{DefIdOrName, Obligation, ObligationCause, ObligationCauseCode, PredicateObligation};
3+
use super::{
4+
DefIdOrName, FindExprBySpan, Obligation, ObligationCause, ObligationCauseCode,
5+
PredicateObligation,
6+
};
47

58
use crate::autoderef::Autoderef;
69
use crate::infer::InferCtxt;
@@ -196,6 +199,13 @@ pub trait TypeErrCtxtExt<'tcx> {
196199
trait_pred: ty::PolyTraitPredicate<'tcx>,
197200
) -> bool;
198201

202+
fn check_for_binding_assigned_block_without_tail_expression(
203+
&self,
204+
obligation: &PredicateObligation<'tcx>,
205+
err: &mut Diagnostic,
206+
trait_pred: ty::PolyTraitPredicate<'tcx>,
207+
);
208+
199209
fn suggest_add_reference_to_arg(
200210
&self,
201211
obligation: &PredicateObligation<'tcx>,
@@ -1032,6 +1042,66 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
10321042
true
10331043
}
10341044

1045+
fn check_for_binding_assigned_block_without_tail_expression(
1046+
&self,
1047+
obligation: &PredicateObligation<'tcx>,
1048+
err: &mut Diagnostic,
1049+
trait_pred: ty::PolyTraitPredicate<'tcx>,
1050+
) {
1051+
let mut span = obligation.cause.span;
1052+
while span.from_expansion() {
1053+
// Remove all the desugaring and macro contexts.
1054+
span.remove_mark();
1055+
}
1056+
let mut expr_finder = FindExprBySpan::new(span);
1057+
let Some(hir::Node::Expr(body)) = self.tcx.hir().find(obligation.cause.body_id) else { return; };
1058+
expr_finder.visit_expr(&body);
1059+
let Some(expr) = expr_finder.result else { return; };
1060+
let Some(typeck) = &self.typeck_results else { return; };
1061+
let Some(ty) = typeck.expr_ty_adjusted_opt(expr) else { return; };
1062+
if !ty.is_unit() {
1063+
return;
1064+
};
1065+
let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { return; };
1066+
let hir::def::Res::Local(hir_id) = path.res else { return; };
1067+
let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else {
1068+
return;
1069+
};
1070+
let Some(hir::Node::Local(hir::Local {
1071+
ty: None,
1072+
init: Some(init),
1073+
..
1074+
})) = self.tcx.hir().find_parent(pat.hir_id) else { return; };
1075+
let hir::ExprKind::Block(block, None) = init.kind else { return; };
1076+
if block.expr.is_some() {
1077+
return;
1078+
}
1079+
let [.., stmt] = block.stmts else {
1080+
err.span_label(block.span, "this empty block is missing a tail expression");
1081+
return;
1082+
};
1083+
let hir::StmtKind::Semi(tail_expr) = stmt.kind else { return; };
1084+
let Some(ty) = typeck.expr_ty_opt(tail_expr) else {
1085+
err.span_label(block.span, "this block is missing a tail expression");
1086+
return;
1087+
};
1088+
let ty = self.resolve_numeric_literals_with_default(self.resolve_vars_if_possible(ty));
1089+
let trait_pred_and_self = trait_pred.map_bound(|trait_pred| (trait_pred, ty));
1090+
1091+
let new_obligation =
1092+
self.mk_trait_obligation_with_new_self_ty(obligation.param_env, trait_pred_and_self);
1093+
if self.predicate_must_hold_modulo_regions(&new_obligation) {
1094+
err.span_suggestion_short(
1095+
stmt.span.with_lo(tail_expr.span.hi()),
1096+
"remove this semicolon",
1097+
"",
1098+
Applicability::MachineApplicable,
1099+
);
1100+
} else {
1101+
err.span_label(block.span, "this block is missing a tail expression");
1102+
}
1103+
}
1104+
10351105
fn suggest_add_reference_to_arg(
10361106
&self,
10371107
obligation: &PredicateObligation<'tcx>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
struct S;
2+
fn main() {
3+
let x = {
4+
println!("foo");
5+
42;
6+
};
7+
let y = {};
8+
let z = {
9+
"hi";
10+
};
11+
let s = {
12+
S;
13+
};
14+
println!("{}", x); //~ ERROR E0277
15+
println!("{}", y); //~ ERROR E0277
16+
println!("{}", z); //~ ERROR E0277
17+
println!("{}", s); //~ ERROR E0277
18+
let _: i32 = x; //~ ERROR E0308
19+
let _: i32 = y; //~ ERROR E0308
20+
let _: i32 = z; //~ ERROR E0308
21+
let _: i32 = s; //~ ERROR E0308
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
error[E0277]: `()` doesn't implement `std::fmt::Display`
2+
--> $DIR/binding-assigned-block-without-tail-expression.rs:14:20
3+
|
4+
LL | 42;
5+
| - help: remove this semicolon
6+
...
7+
LL | println!("{}", x);
8+
| ^ `()` cannot be formatted with the default formatter
9+
|
10+
= help: the trait `std::fmt::Display` is not implemented for `()`
11+
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
12+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
13+
14+
error[E0277]: `()` doesn't implement `std::fmt::Display`
15+
--> $DIR/binding-assigned-block-without-tail-expression.rs:15:20
16+
|
17+
LL | let y = {};
18+
| -- this empty block is missing a tail expression
19+
...
20+
LL | println!("{}", y);
21+
| ^ `()` cannot be formatted with the default formatter
22+
|
23+
= help: the trait `std::fmt::Display` is not implemented for `()`
24+
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
25+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
26+
27+
error[E0277]: `()` doesn't implement `std::fmt::Display`
28+
--> $DIR/binding-assigned-block-without-tail-expression.rs:16:20
29+
|
30+
LL | "hi";
31+
| - help: remove this semicolon
32+
...
33+
LL | println!("{}", z);
34+
| ^ `()` cannot be formatted with the default formatter
35+
|
36+
= help: the trait `std::fmt::Display` is not implemented for `()`
37+
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
38+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
39+
40+
error[E0277]: `()` doesn't implement `std::fmt::Display`
41+
--> $DIR/binding-assigned-block-without-tail-expression.rs:17:20
42+
|
43+
LL | let s = {
44+
| _____________-
45+
LL | | S;
46+
LL | | };
47+
| |_____- this block is missing a tail expression
48+
...
49+
LL | println!("{}", s);
50+
| ^ `()` cannot be formatted with the default formatter
51+
|
52+
= help: the trait `std::fmt::Display` is not implemented for `()`
53+
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
54+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
55+
56+
error[E0308]: mismatched types
57+
--> $DIR/binding-assigned-block-without-tail-expression.rs:18:18
58+
|
59+
LL | 42;
60+
| - help: remove this semicolon
61+
...
62+
LL | let _: i32 = x;
63+
| --- ^ expected `i32`, found `()`
64+
| |
65+
| expected due to this
66+
67+
error[E0308]: mismatched types
68+
--> $DIR/binding-assigned-block-without-tail-expression.rs:19:18
69+
|
70+
LL | let y = {};
71+
| -- this empty block is missing a tail expression
72+
...
73+
LL | let _: i32 = y;
74+
| --- ^ expected `i32`, found `()`
75+
| |
76+
| expected due to this
77+
78+
error[E0308]: mismatched types
79+
--> $DIR/binding-assigned-block-without-tail-expression.rs:20:18
80+
|
81+
LL | let z = {
82+
| _____________-
83+
LL | | "hi";
84+
LL | | };
85+
| |_____- this block is missing a tail expression
86+
...
87+
LL | let _: i32 = z;
88+
| --- ^ expected `i32`, found `()`
89+
| |
90+
| expected due to this
91+
92+
error[E0308]: mismatched types
93+
--> $DIR/binding-assigned-block-without-tail-expression.rs:21:18
94+
|
95+
LL | let s = {
96+
| _____________-
97+
LL | | S;
98+
LL | | };
99+
| |_____- this block is missing a tail expression
100+
...
101+
LL | let _: i32 = s;
102+
| --- ^ expected `i32`, found `()`
103+
| |
104+
| expected due to this
105+
106+
error: aborting due to 8 previous errors
107+
108+
Some errors have detailed explanations: E0277, E0308.
109+
For more information about an error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)