Skip to content

Commit 86fb983

Browse files
authored
Rollup merge of rust-lang#81458 - estebank:match-stmt-remove-semi, r=oli-obk
Detect match statement intended to be tail expression CC rust-lang#24157
2 parents c0a54cc + 1d24f07 commit 86fb983

6 files changed

+161
-10
lines changed

compiler/rustc_typeck/src/check/_match.rs

+60-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use crate::check::coercion::{AsCoercionSite, CoerceMany};
22
use crate::check::{Diverges, Expectation, FnCtxt, Needs};
3+
use rustc_errors::{Applicability, DiagnosticBuilder};
34
use rustc_hir::{self as hir, ExprKind};
45
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
56
use rustc_infer::traits::Obligation;
67
use rustc_middle::ty::{self, ToPredicate, Ty, TyS};
7-
use rustc_span::Span;
8+
use rustc_span::{MultiSpan, Span};
89
use rustc_trait_selection::opaque_types::InferCtxtExt as _;
910
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
1011
use rustc_trait_selection::traits::{
@@ -206,7 +207,64 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
206207
),
207208
};
208209
let cause = self.cause(span, code);
209-
coercion.coerce(self, &cause, &arm.body, arm_ty);
210+
let can_coerce_to_return_ty = match self.ret_coercion.as_ref() {
211+
Some(ret_coercion) if self.in_tail_expr => {
212+
let ret_ty = ret_coercion.borrow().expected_ty();
213+
let ret_ty = self.inh.infcx.shallow_resolve(ret_ty);
214+
self.can_coerce(arm_ty, ret_ty)
215+
&& prior_arm_ty.map_or(true, |t| self.can_coerce(t, ret_ty))
216+
// The match arms need to unify for the case of `impl Trait`.
217+
&& !matches!(ret_ty.kind(), ty::Opaque(..))
218+
}
219+
_ => false,
220+
};
221+
222+
// This is the moral equivalent of `coercion.coerce(self, cause, arm.body, arm_ty)`.
223+
// We use it this way to be able to expand on the potential error and detect when a
224+
// `match` tail statement could be a tail expression instead. If so, we suggest
225+
// removing the stray semicolon.
226+
coercion.coerce_inner(
227+
self,
228+
&cause,
229+
Some(&arm.body),
230+
arm_ty,
231+
Some(&mut |err: &mut DiagnosticBuilder<'_>| {
232+
if let (Expectation::IsLast(stmt), Some(ret), true) =
233+
(orig_expected, self.ret_type_span, can_coerce_to_return_ty)
234+
{
235+
let semi_span = expr.span.shrink_to_hi().with_hi(stmt.hi());
236+
let mut ret_span: MultiSpan = semi_span.into();
237+
ret_span.push_span_label(
238+
expr.span,
239+
"this could be implicitly returned but it is a statement, not a \
240+
tail expression"
241+
.to_owned(),
242+
);
243+
ret_span.push_span_label(
244+
ret,
245+
"the `match` arms can conform to this return type".to_owned(),
246+
);
247+
ret_span.push_span_label(
248+
semi_span,
249+
"the `match` is a statement because of this semicolon, consider \
250+
removing it"
251+
.to_owned(),
252+
);
253+
err.span_note(
254+
ret_span,
255+
"you might have meant to return the `match` expression",
256+
);
257+
err.tool_only_span_suggestion(
258+
semi_span,
259+
"remove this semicolon",
260+
String::new(),
261+
Applicability::MaybeIncorrect,
262+
);
263+
}
264+
}),
265+
false,
266+
);
267+
210268
other_arms.push(arm_span);
211269
if other_arms.len() > 5 {
212270
other_arms.remove(0);

compiler/rustc_typeck/src/check/coercion.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
12371237
/// The inner coercion "engine". If `expression` is `None`, this
12381238
/// is a forced-unit case, and hence `expression_ty` must be
12391239
/// `Nil`.
1240-
fn coerce_inner<'a>(
1240+
crate fn coerce_inner<'a>(
12411241
&mut self,
12421242
fcx: &FnCtxt<'a, 'tcx>,
12431243
cause: &ObligationCause<'tcx>,

compiler/rustc_typeck/src/check/expectation.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub enum Expectation<'tcx> {
2121
/// This rvalue expression will be wrapped in `&` or `Box` and coerced
2222
/// to `&Ty` or `Box<Ty>`, respectively. `Ty` is `[A]` or `Trait`.
2323
ExpectRvalueLikeUnsized(Ty<'tcx>),
24+
25+
IsLast(Span),
2426
}
2527

2628
impl<'a, 'tcx> Expectation<'tcx> {
@@ -79,19 +81,20 @@ impl<'a, 'tcx> Expectation<'tcx> {
7981

8082
// Resolves `expected` by a single level if it is a variable. If
8183
// there is no expected type or resolution is not possible (e.g.,
82-
// no constraints yet present), just returns `None`.
84+
// no constraints yet present), just returns `self`.
8385
fn resolve(self, fcx: &FnCtxt<'a, 'tcx>) -> Expectation<'tcx> {
8486
match self {
8587
NoExpectation => NoExpectation,
8688
ExpectCastableToType(t) => ExpectCastableToType(fcx.resolve_vars_if_possible(t)),
8789
ExpectHasType(t) => ExpectHasType(fcx.resolve_vars_if_possible(t)),
8890
ExpectRvalueLikeUnsized(t) => ExpectRvalueLikeUnsized(fcx.resolve_vars_if_possible(t)),
91+
IsLast(sp) => IsLast(sp),
8992
}
9093
}
9194

9295
pub(super) fn to_option(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
9396
match self.resolve(fcx) {
94-
NoExpectation => None,
97+
NoExpectation | IsLast(_) => None,
9598
ExpectCastableToType(ty) | ExpectHasType(ty) | ExpectRvalueLikeUnsized(ty) => Some(ty),
9699
}
97100
}
@@ -103,7 +106,9 @@ impl<'a, 'tcx> Expectation<'tcx> {
103106
pub(super) fn only_has_type(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
104107
match self.resolve(fcx) {
105108
ExpectHasType(ty) => Some(ty),
106-
NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) => None,
109+
NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) | IsLast(_) => {
110+
None
111+
}
107112
}
108113
}
109114

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

+11-4
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
539539
self.overwrite_local_ty_if_err(local, ty, pat_ty);
540540
}
541541

542-
pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>) {
542+
pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>, is_last: bool) {
543543
// Don't do all the complex logic below for `DeclItem`.
544544
match stmt.kind {
545545
hir::StmtKind::Item(..) => return,
@@ -567,7 +567,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
567567
});
568568
}
569569
hir::StmtKind::Semi(ref expr) => {
570-
self.check_expr(&expr);
570+
// All of this is equivalent to calling `check_expr`, but it is inlined out here
571+
// in order to capture the fact that this `match` is the last statement in its
572+
// function. This is done for better suggestions to remove the `;`.
573+
let expectation = match expr.kind {
574+
hir::ExprKind::Match(..) if is_last => IsLast(stmt.span),
575+
_ => NoExpectation,
576+
};
577+
self.check_expr_with_expectation(expr, expectation);
571578
}
572579
}
573580

@@ -626,8 +633,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
626633
let ctxt = BreakableCtxt { coerce: Some(coerce), may_break: false };
627634

628635
let (ctxt, ()) = self.with_breakable_ctxt(blk.hir_id, ctxt, || {
629-
for s in blk.stmts {
630-
self.check_stmt(s);
636+
for (pos, s) in blk.stmts.iter().enumerate() {
637+
self.check_stmt(s, blk.stmts.len() - 1 == pos);
631638
}
632639

633640
// check the tail expression **without** holding the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
pub trait Foo {}
2+
3+
struct Bar;
4+
struct Baz;
5+
6+
impl Foo for Bar { }
7+
impl Foo for Baz { }
8+
9+
fn not_all_paths(a: &str) -> u32 { //~ ERROR mismatched types
10+
match a {
11+
"baz" => 0,
12+
_ => 1,
13+
};
14+
}
15+
16+
fn right(b: &str) -> Box<dyn Foo> {
17+
match b {
18+
"baz" => Box::new(Baz),
19+
_ => Box::new(Bar),
20+
}
21+
}
22+
23+
fn wrong(c: &str) -> Box<dyn Foo> { //~ ERROR mismatched types
24+
match c {
25+
"baz" => Box::new(Baz),
26+
_ => Box::new(Bar), //~ ERROR `match` arms have incompatible types
27+
};
28+
}
29+
30+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/match-with-different-arm-types-as-stmt-instead-of-expr.rs:9:30
3+
|
4+
LL | fn not_all_paths(a: &str) -> u32 {
5+
| ------------- ^^^ expected `u32`, found `()`
6+
| |
7+
| implicitly returns `()` as its body has no tail or `return` expression
8+
...
9+
LL | };
10+
| - help: consider removing this semicolon
11+
12+
error[E0308]: `match` arms have incompatible types
13+
--> $DIR/match-with-different-arm-types-as-stmt-instead-of-expr.rs:26:14
14+
|
15+
LL | / match c {
16+
LL | | "baz" => Box::new(Baz),
17+
| | ------------- this is found to be of type `Box<Baz>`
18+
LL | | _ => Box::new(Bar),
19+
| | ^^^^^^^^^^^^^ expected struct `Baz`, found struct `Bar`
20+
LL | | };
21+
| |_____- `match` arms have incompatible types
22+
|
23+
= note: expected type `Box<Baz>`
24+
found struct `Box<Bar>`
25+
note: you might have meant to return the `match` expression
26+
--> $DIR/match-with-different-arm-types-as-stmt-instead-of-expr.rs:27:6
27+
|
28+
LL | fn wrong(c: &str) -> Box<dyn Foo> {
29+
| ------------ the `match` arms can conform to this return type
30+
LL | / match c {
31+
LL | | "baz" => Box::new(Baz),
32+
LL | | _ => Box::new(Bar),
33+
LL | | };
34+
| | -^ the `match` is a statement because of this semicolon, consider removing it
35+
| |_____|
36+
| this could be implicitly returned but it is a statement, not a tail expression
37+
38+
error[E0308]: mismatched types
39+
--> $DIR/match-with-different-arm-types-as-stmt-instead-of-expr.rs:23:22
40+
|
41+
LL | fn wrong(c: &str) -> Box<dyn Foo> {
42+
| ----- ^^^^^^^^^^^^ expected struct `Box`, found `()`
43+
| |
44+
| implicitly returns `()` as its body has no tail or `return` expression
45+
|
46+
= note: expected struct `Box<(dyn Foo + 'static)>`
47+
found unit type `()`
48+
49+
error: aborting due to 3 previous errors
50+
51+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)