Change:
The code below has superfluous semicolons in the branches of the if expression: The error message that results is: This can be confusing to beginners, who may not understand unit type. However, since the expressions and can be shown to have no side effects, the code (as is) can't be what the user intended. Could the compiler produce a more targeted error message that mentions the superfluous semicolon? <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
label T-compiler A-diagnostics D-newcomer-roadblock D-terse
claim
Thinking more about this, I don't think we should extend the "remove semicolon to make tail expression" suggestion to cover this case, since we don't know the expected type of at the point at which the suggestion would be made. For example, we don't make the suggestion to change to here: But that's just a guess based on my limited knowledge of type mismatch suggestions. Maybe someone else could weigh in?

Discussion:
Ty::new_unit(self.tcx), ); } <del> if !self.consider_removing_semicolon(blk, expected_ty, err) { </del> <ins> if !self.err_ctxt().consider_removing_semicolon( blk, expected_ty, err, ) { </ins> self.err_ctxt().consider_returning_binding( blk, expected_ty,

Path, QPath, Stmt, StmtKind, TyKind, WherePredicate, }; use rustc_hir_analysis::astconv::AstConv; <del> use rustc_infer::traits::{self, StatementAsExpression}; </del> <ins> use rustc_infer::traits::{self}; </ins> use rustc_middle::lint::in_external_macro; use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::print::with_no_trimmed_paths;

} } <del> /// A common error is to add an extra semicolon: /// /// ```compile_fail,E0308 /// fn foo() -> usize { ///     22; /// } /// ``` /// /// This routine checks if the final statement in a block is an /// expression with an explicit semicolon whose type is compatible /// with `expected_ty`. If so, it suggests removing the semicolon. pub(crate) fn consider_removing_semicolon( &self, blk: &'tcx hir::Block<'tcx>, expected_ty: Ty<'tcx>, err: &mut Diag<'_>, ) -> bool { if let Some((span_semi, boxed)) = self.err_ctxt().could_remove_semicolon(blk, expected_ty) { if let StatementAsExpression::NeedsBoxing = boxed { err.span_suggestion_verbose( span_semi, "consider removing this semicolon and boxing the expression", "", Applicability::HasPlaceholders, ); } else { err.span_suggestion_short( span_semi, "remove this semicolon to return this value", "", Applicability::MachineApplicable, ); } true } else { false } } </del> pub(crate) fn is_field_suggestable( &self, field: &ty::FieldDef,

self.suggest_accessing_field_where_appropriate(cause, &exp_found, diag); self.suggest_await_on_expect_found(cause, span, &exp_found, diag); self.suggest_function_pointers(cause, span, &exp_found, diag); <ins> self.suggest_turning_stmt_into_expr(cause, &exp_found, diag); </ins> } }

<ins> use crate::infer::error_reporting::hir::Path; </ins> use hir::def::CtorKind; use hir::intravisit::{walk_expr, walk_stmt, Visitor}; <ins> use hir::{Local, QPath}; </ins> use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{Applicability, Diag}; use rustc_hir as hir; <ins> use rustc_hir::def::Res; use rustc_hir::MatchSource; use rustc_hir::Node; </ins> use rustc_middle::traits::{ IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, StatementAsExpression,

} } <ins> pub(super) fn suggest_turning_stmt_into_expr( &self, cause: &ObligationCause<'tcx>, exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, diag: &mut Diag<'_>, ) { let ty::error::ExpectedFound { expected, found } = exp_found; if !found.peel_refs().is_unit() { return; } let ObligationCauseCode::BlockTailExpression(hir_id, MatchSource::Normal) = cause.code() else { return; }; let node = self.tcx.hir_node(*hir_id); let mut blocks = vec![]; if let hir::Node::Block(block) = node && let Some(expr) = block.expr && let hir::ExprKind::Path(QPath::Resolved(_, Path { res, .. })) = expr.kind && let Res::Local(local) = res && let Node::Local(Local { init: Some(init), .. }) = self.tcx.parent_hir_node(*local) { fn collect_blocks<'hir>(expr: &hir::Expr<'hir>, blocks: &mut Vec<&hir::Block<'hir>>) { match expr.kind { // `blk1` and `blk2` must be have the same types, it will be reported before reaching here hir::ExprKind::If(_, blk1, Some(blk2)) => { collect_blocks(blk1, blocks); collect_blocks(blk2, blocks); } hir::ExprKind::Match(_, arms, _) => { // all arms must have same types for arm in arms.iter() { collect_blocks(arm.body, blocks); } } hir::ExprKind::Block(blk, _) => { blocks.push(blk); } _ => {} } } collect_blocks(init, &mut blocks); } let expected_inner: Ty<'_> = expected.peel_refs(); for block in blocks.iter() { self.consider_removing_semicolon(block, expected_inner, diag); } } /// A common error is to add an extra semicolon: /// /// ```compile_fail,E0308 /// fn foo() -> usize { ///     22; /// } /// ``` /// /// This routine checks if the final statement in a block is an /// expression with an explicit semicolon whose type is compatible /// with `expected_ty`. If so, it suggests removing the semicolon. pub fn consider_removing_semicolon( &self, blk: &'tcx hir::Block<'tcx>, expected_ty: Ty<'tcx>, diag: &mut Diag<'_>, ) -> bool { if let Some((span_semi, boxed)) = self.could_remove_semicolon(blk, expected_ty) { if let StatementAsExpression::NeedsBoxing = boxed { diag.span_suggestion_verbose( span_semi, "consider removing this semicolon and boxing the expression", "", Applicability::HasPlaceholders, ); } else { diag.span_suggestion_short( span_semi, "remove this semicolon to return this value", "", Applicability::MachineApplicable, ); } true } else { false } } </ins> pub(super) fn suggest_function_pointers( &self, cause: &ObligationCause<'tcx>,

<ins> #![allow(unused)] fn test_if() -> i32 { let x = if true { eprintln!("hello"); 3; } else { 4; }; x //~ ERROR mismatched types } fn test_if_without_binding() -> i32 { if true { //~ ERROR mismatched types eprintln!("hello"); 3; } else { //~ ERROR mismatched types 4; } } fn test_match() -> i32 { let v = 1; let res = match v { 1 => { 1; } _ => { 2; } }; res //~ ERROR mismatched types } fn test_match_match_without_binding() -> i32 { let v = 1; match v { 1 => { 1; } //~ ERROR mismatched types _ => { 2; } //~ ERROR mismatched types } } fn test_match_arm_different_types() -> i32 { let v = 1; let res = match v { 1 => { if 1 < 2 { 1 } else { 2 } } _ => { 2; } //~ ERROR `match` arms have incompatible types }; res } fn test_if_match_mixed() -> i32 { let x = if true { 3; } else { match 1 { 1 => { 1 } _ => { 2 } }; }; x //~ ERROR mismatched types } fn test_if_match_mixed_failed() -> i32 { let x = if true { 3; } else { // because this is a tailed expr, so we won't check deeper match 1 { 1 => { 33; } _ => { 44; } } }; x //~ ERROR mismatched types } fn main() {} </ins>

<ins> error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:11:5 | LL | fn test_if() -> i32 { |                 --- expected `i32` because of return type ... LL |     x |     ^ expected `i32`, found `()` | help: remove this semicolon to return this value | LL -         3; LL +         3 | help: remove this semicolon to return this value | LL -         4; LL +         4 | error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:15:13 | LL |       if true { |  _____________^ LL | |         eprintln!("hello"); LL | |         3; | |          - help: remove this semicolon to return this value LL | |     } | |_____^ expected `i32`, found `()` error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:19:10 | LL |       else { |  __________^ LL | |         4; | |          - help: remove this semicolon to return this value LL | |     } | |_____^ expected `i32`, found `()` error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:30:5 | LL | fn test_match() -> i32 { |                    --- expected `i32` because of return type ... LL |     res |     ^^^ expected `i32`, found `()` | help: remove this semicolon to return this value | LL -         1 => { 1; } LL +         1 => { 1 } | help: remove this semicolon to return this value | LL -         _ => { 2; } LL +         _ => { 2 } | error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:36:14 | LL |         1 => { 1; } |              ^^^-^^ |              |  | |              |  help: remove this semicolon to return this value |              expected `i32`, found `()` error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:37:14 | LL |         _ => { 2; } |              ^^^-^^ |              |  | |              |  help: remove this semicolon to return this value |              expected `i32`, found `()` error[E0308]: `match` arms have incompatible types --> $DIR/stmts-as-exp-105431.rs:45:16 | LL |       let res = match v { |  _______________- LL | |         1 => { if 1 < 2 { 1 } else { 2 } } | |                ------------------------- this is found to be of type `{integer}` LL | |         _ => { 2; } | |                ^- | |                || | |                |help: consider removing this semicolon | |                expected integer, found `()` LL | |     }; | |_____- `match` arms have incompatible types error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:59:5 | LL | fn test_if_match_mixed() -> i32 { |                             --- expected `i32` because of return type ... LL |     x |     ^ expected `i32`, found `()` | help: remove this semicolon to return this value | LL -         3; LL +         3 | help: remove this semicolon to return this value | LL -         }; LL +         } | error[E0308]: mismatched types --> $DIR/stmts-as-exp-105431.rs:72:5 | LL | fn test_if_match_mixed_failed() -> i32 { |                                    --- expected `i32` because of return type LL |     let x = if true { LL |         3; |          - help: remove this semicolon to return this value ... LL |     x |     ^ expected `i32`, found `()` error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0308`. </ins>