Skip to content

Commit

Permalink
Account for ! arm in tail match expr
Browse files Browse the repository at this point in the history
On functions with a default return type that influences the coerced type
of `match` arms, check if the failing arm is actually of type `!`. If
so, suggest changing the return type so the coercion against the prior
arms is successful.

```
error[E0308]: `match` arms have incompatible types
  --> $DIR/match-tail-expr-never-type-error.rs:9:13
   |
LL |   fn bar(a: bool) {
   |                  - help: try adding a return type: `-> i32`
LL | /     match a {
LL | |         true => 1,
   | |                 - this is found to be of type `{integer}`
LL | |         false => {
LL | |             never()
   | |             ^^^^^^^
   | |             |
   | |             expected integer, found `()`
   | |             this expression is of type `!`, but it get's coerced to `()` due to its surrounding expression
LL | |         }
LL | |     }
   | |_____- `match` arms have incompatible types
```

Fix rust-lang#24157.
  • Loading branch information
estebank authored and Kjetil Kjeka committed Nov 29, 2023
1 parent 4cc8000 commit cff97a5
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 4 deletions.
36 changes: 35 additions & 1 deletion compiler/rustc_hir_typeck/src/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&cause,
Some(arm.body),
arm_ty,
|err| self.suggest_removing_semicolon_for_coerce(err, expr, arm_ty, prior_arm),
|err| {
self.explain_never_type_coerced_to_unit(err, arm, arm_ty, prior_arm, expr);
},
false,
);

Expand Down Expand Up @@ -177,6 +179,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
coercion.complete(self)
}

fn explain_never_type_coerced_to_unit(
&self,
err: &mut Diagnostic,
arm: &hir::Arm<'tcx>,
arm_ty: Ty<'tcx>,
prior_arm: Option<(Option<hir::HirId>, Ty<'tcx>, Span)>,
expr: &hir::Expr<'tcx>,
) {
if let hir::ExprKind::Block(block, _) = arm.body.kind
&& let Some(expr) = block.expr
&& let arm_tail_ty = self.node_ty(expr.hir_id)
&& arm_tail_ty.is_never()
&& !arm_ty.is_never()
{
err.span_label(
expr.span,
format!(
"this expression is of type `!`, but it is coerced to `{arm_ty}` due to its \
surrounding expression",
),
);
self.suggest_mismatched_types_on_tail(
err,
expr,
arm_ty,
prior_arm.map_or(arm_tail_ty, |(_, ty, _)| ty),
expr.hir_id,
);
}
self.suggest_removing_semicolon_for_coerce(err, expr, arm_ty, prior_arm)
}

fn suggest_removing_semicolon_for_coerce(
&self,
diag: &mut Diagnostic,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
// label pointing out the cause for the type coercion will be wrong
// as prior return coercions would not be relevant (#57664).
let fn_decl = if let (Some(expr), Some(blk_id)) = (expression, blk_id) {
fcx.suggest_missing_semicolon(&mut err, expr, expected, false);
let pointing_at_return_type =
fcx.suggest_mismatched_types_on_tail(&mut err, expr, expected, found, blk_id);
if let (Some(cond_expr), true, false) = (
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
coerce.coerce_forced_unit(
self,
&cause,
|err| {
self.suggest_mismatched_types_on_tail(err, expr, ty, e_ty, target_id);
|mut err| {
self.suggest_missing_semicolon(&mut err, expr, e_ty, false);
self.suggest_mismatched_types_on_tail(
&mut err, expr, ty, e_ty, target_id,
);
let error = Some(Sorts(ExpectedFound { expected: ty, found: e_ty }));
self.annotate_loop_expected_due_to_inference(err, expr, error);
if let Some(val) = ty_kind_suggestion(ty) {
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
blk_id: hir::HirId,
) -> bool {
let expr = expr.peel_drop_temps();
self.suggest_missing_semicolon(err, expr, expected, false);
let mut pointing_at_return_type = false;
if let hir::ExprKind::Break(..) = expr.kind {
// `break` type mismatches provide better context for tail `loop` expressions.
Expand Down
16 changes: 16 additions & 0 deletions tests/ui/match/match-tail-expr-never-type-error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fn never() -> ! {
loop {}
}

fn bar(a: bool) {
match a {
true => 1,
false => {
never() //~ ERROR `match` arms have incompatible types
}
}
}
fn main() {
bar(true);
bar(false);
}
21 changes: 21 additions & 0 deletions tests/ui/match/match-tail-expr-never-type-error.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0308]: `match` arms have incompatible types
--> $DIR/match-tail-expr-never-type-error.rs:9:13
|
LL | fn bar(a: bool) {
| - help: try adding a return type: `-> i32`
LL | / match a {
LL | | true => 1,
| | - this is found to be of type `{integer}`
LL | | false => {
LL | | never()
| | ^^^^^^^
| | |
| | expected integer, found `()`
| | this expression is of type `!`, but it is coerced to `()` due to its surrounding expression
LL | | }
LL | | }
| |_____- `match` arms have incompatible types

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.

0 comments on commit cff97a5

Please sign in to comment.