11use crate :: coercion:: { AsCoercionSite , CoerceMany } ;
22use crate :: { Diverges , Expectation , FnCtxt , Needs } ;
3- use rustc_errors:: Diagnostic ;
4- use rustc_hir:: { self as hir, ExprKind } ;
3+ use rustc_errors:: { Applicability , Diagnostic } ;
4+ use rustc_hir:: {
5+ self as hir,
6+ def:: { CtorOf , DefKind , Res } ,
7+ ExprKind , PatKind ,
8+ } ;
59use rustc_hir_pretty:: ty_to_string;
610use rustc_infer:: infer:: type_variable:: { TypeVariableOrigin , TypeVariableOriginKind } ;
711use rustc_infer:: traits:: Obligation ;
@@ -273,7 +277,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
273277 /// Returns `true` if there was an error forcing the coercion to the `()` type.
274278 pub ( super ) fn if_fallback_coercion < T > (
275279 & self ,
276- span : Span ,
280+ if_span : Span ,
281+ cond_expr : & ' tcx hir:: Expr < ' tcx > ,
277282 then_expr : & ' tcx hir:: Expr < ' tcx > ,
278283 coercion : & mut CoerceMany < ' tcx , ' _ , T > ,
279284 ) -> bool
@@ -283,23 +288,88 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
283288 // If this `if` expr is the parent's function return expr,
284289 // the cause of the type coercion is the return type, point at it. (#25228)
285290 let hir_id = self . tcx . hir ( ) . parent_id ( self . tcx . hir ( ) . parent_id ( then_expr. hir_id ) ) ;
286- let ret_reason = self . maybe_get_coercion_reason ( hir_id, span ) ;
287- let cause = self . cause ( span , ObligationCauseCode :: IfExpressionWithNoElse ) ;
291+ let ret_reason = self . maybe_get_coercion_reason ( hir_id, if_span ) ;
292+ let cause = self . cause ( if_span , ObligationCauseCode :: IfExpressionWithNoElse ) ;
288293 let mut error = false ;
289294 coercion. coerce_forced_unit (
290295 self ,
291296 & cause,
292297 |err| {
293- if let Some ( ( span , msg) ) = & ret_reason {
294- err. span_label ( * span , msg. clone ( ) ) ;
295- } else if let ExprKind :: Block ( block, _) = & then_expr. kind
296- && let Some ( expr) = & block. expr
298+ if let Some ( ( if_span , msg) ) = & ret_reason {
299+ err. span_label ( * if_span , msg. clone ( ) ) ;
300+ } else if let ExprKind :: Block ( block, _) = then_expr. kind
301+ && let Some ( expr) = block. expr
297302 {
298303 err. span_label ( expr. span , "found here" ) ;
299304 }
300305 err. note ( "`if` expressions without `else` evaluate to `()`" ) ;
301306 err. help ( "consider adding an `else` block that evaluates to the expected type" ) ;
302307 error = true ;
308+ if let ExprKind :: Let ( hir:: Let { span, pat, init, .. } ) = cond_expr. kind
309+ && let ExprKind :: Block ( block, _) = then_expr. kind
310+ // Refutability checks occur on the MIR, so we approximate it here by checking
311+ // if we have an enum with a single variant or a struct in the pattern.
312+ && let PatKind :: TupleStruct ( qpath, ..) | PatKind :: Struct ( qpath, ..) = pat. kind
313+ && let hir:: QPath :: Resolved ( _, path) = qpath
314+ {
315+ match path. res {
316+ Res :: Def ( DefKind :: Ctor ( CtorOf :: Struct , _) , _) => {
317+ // Structs are always irrefutable. Their fields might not be, but we
318+ // don't check for that here, it's only an approximation.
319+ }
320+ Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , _) , def_id)
321+ if self
322+ . tcx
323+ . adt_def ( self . tcx . parent ( self . tcx . parent ( def_id) ) )
324+ . variants ( )
325+ . len ( )
326+ == 1 =>
327+ {
328+ // There's only a single variant in the `enum`, so we can suggest the
329+ // irrefutable `let` instead of `if let`.
330+ }
331+ _ => return ,
332+ }
333+
334+ let mut sugg = vec ! [
335+ // Remove the `if`
336+ ( if_span. until( * span) , String :: new( ) ) ,
337+ ] ;
338+ match ( block. stmts , block. expr ) {
339+ ( [ first, ..] , Some ( expr) ) => {
340+ let padding = self
341+ . tcx
342+ . sess
343+ . source_map ( )
344+ . indentation_before ( first. span )
345+ . unwrap_or_else ( || String :: new ( ) ) ;
346+ sugg. extend ( [
347+ ( init. span . between ( first. span ) , format ! ( ";\n {padding}" ) ) ,
348+ ( expr. span . shrink_to_hi ( ) . with_hi ( block. span . hi ( ) ) , String :: new ( ) ) ,
349+ ] ) ;
350+ }
351+ ( [ ] , Some ( expr) ) => {
352+ let padding = self
353+ . tcx
354+ . sess
355+ . source_map ( )
356+ . indentation_before ( expr. span )
357+ . unwrap_or_else ( || String :: new ( ) ) ;
358+ sugg. extend ( [
359+ ( init. span . between ( expr. span ) , format ! ( ";\n {padding}" ) ) ,
360+ ( expr. span . shrink_to_hi ( ) . with_hi ( block. span . hi ( ) ) , String :: new ( ) ) ,
361+ ] ) ;
362+ }
363+ // If there's no value in the body, then the `if` expression would already
364+ // be of type `()`, so checking for those cases is unnecessary.
365+ ( _, None ) => return ,
366+ }
367+ err. multipart_suggestion (
368+ "consider using an irrefutable `let` binding instead" ,
369+ sugg,
370+ Applicability :: MaybeIncorrect ,
371+ ) ;
372+ }
303373 } ,
304374 false ,
305375 ) ;
0 commit comments