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,29 +288,106 @@ 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,
292- |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
297- {
298- err. span_label ( expr. span , "found here" ) ;
299- }
300- err. note ( "`if` expressions without `else` evaluate to `()`" ) ;
301- err. help ( "consider adding an `else` block that evaluates to the expected type" ) ;
302- error = true ;
303- } ,
297+ |err| self . explain_if_expr ( err, ret_reason, if_span, cond_expr, then_expr, & mut error) ,
304298 false ,
305299 ) ;
306300 error
307301 }
308302
303+ /// Explain why `if` expressions without `else` evaluate to `()` and detect likely irrefutable
304+ /// `if let PAT = EXPR {}` expressions that could be turned into `let PAT = EXPR;`.
305+ fn explain_if_expr (
306+ & self ,
307+ err : & mut Diagnostic ,
308+ ret_reason : Option < ( Span , String ) > ,
309+ if_span : Span ,
310+ cond_expr : & ' tcx hir:: Expr < ' tcx > ,
311+ then_expr : & ' tcx hir:: Expr < ' tcx > ,
312+ error : & mut bool ,
313+ ) {
314+ if let Some ( ( if_span, msg) ) = ret_reason {
315+ err. span_label ( if_span, msg. clone ( ) ) ;
316+ } else if let ExprKind :: Block ( block, _) = then_expr. kind
317+ && let Some ( expr) = block. expr
318+ {
319+ err. span_label ( expr. span , "found here" ) ;
320+ }
321+ err. note ( "`if` expressions without `else` evaluate to `()`" ) ;
322+ err. help ( "consider adding an `else` block that evaluates to the expected type" ) ;
323+ * error = true ;
324+ if let ExprKind :: Let ( hir:: Let { span, pat, init, .. } ) = cond_expr. kind
325+ && let ExprKind :: Block ( block, _) = then_expr. kind
326+ // Refutability checks occur on the MIR, so we approximate it here by checking
327+ // if we have an enum with a single variant or a struct in the pattern.
328+ && let PatKind :: TupleStruct ( qpath, ..) | PatKind :: Struct ( qpath, ..) = pat. kind
329+ && let hir:: QPath :: Resolved ( _, path) = qpath
330+ {
331+ match path. res {
332+ Res :: Def ( DefKind :: Ctor ( CtorOf :: Struct , _) , _) => {
333+ // Structs are always irrefutable. Their fields might not be, but we
334+ // don't check for that here, it's only an approximation.
335+ }
336+ Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , _) , def_id)
337+ if self
338+ . tcx
339+ . adt_def ( self . tcx . parent ( self . tcx . parent ( def_id) ) )
340+ . variants ( )
341+ . len ( )
342+ == 1 =>
343+ {
344+ // There's only a single variant in the `enum`, so we can suggest the
345+ // irrefutable `let` instead of `if let`.
346+ }
347+ _ => return ,
348+ }
349+
350+ let mut sugg = vec ! [
351+ // Remove the `if`
352+ ( if_span. until( * span) , String :: new( ) ) ,
353+ ] ;
354+ match ( block. stmts , block. expr ) {
355+ ( [ first, ..] , Some ( expr) ) => {
356+ let padding = self
357+ . tcx
358+ . sess
359+ . source_map ( )
360+ . indentation_before ( first. span )
361+ . unwrap_or_else ( || String :: new ( ) ) ;
362+ sugg. extend ( [
363+ ( init. span . between ( first. span ) , format ! ( ";\n {padding}" ) ) ,
364+ ( expr. span . shrink_to_hi ( ) . with_hi ( block. span . hi ( ) ) , String :: new ( ) ) ,
365+ ] ) ;
366+ }
367+ ( [ ] , Some ( expr) ) => {
368+ let padding = self
369+ . tcx
370+ . sess
371+ . source_map ( )
372+ . indentation_before ( expr. span )
373+ . unwrap_or_else ( || String :: new ( ) ) ;
374+ sugg. extend ( [
375+ ( init. span . between ( expr. span ) , format ! ( ";\n {padding}" ) ) ,
376+ ( expr. span . shrink_to_hi ( ) . with_hi ( block. span . hi ( ) ) , String :: new ( ) ) ,
377+ ] ) ;
378+ }
379+ // If there's no value in the body, then the `if` expression would already
380+ // be of type `()`, so checking for those cases is unnecessary.
381+ ( _, None ) => return ,
382+ }
383+ err. multipart_suggestion (
384+ "consider using an irrefutable `let` binding instead" ,
385+ sugg,
386+ Applicability :: MaybeIncorrect ,
387+ ) ;
388+ }
389+ }
390+
309391 pub fn maybe_get_coercion_reason (
310392 & self ,
311393 hir_id : hir:: HirId ,
0 commit comments