1
1
use crate :: coercion:: { AsCoercionSite , CoerceMany } ;
2
2
use 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
+ } ;
5
9
use rustc_hir_pretty:: ty_to_string;
6
10
use rustc_infer:: infer:: type_variable:: { TypeVariableOrigin , TypeVariableOriginKind } ;
7
11
use rustc_infer:: traits:: Obligation ;
@@ -273,7 +277,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
273
277
/// Returns `true` if there was an error forcing the coercion to the `()` type.
274
278
pub ( super ) fn if_fallback_coercion < T > (
275
279
& self ,
276
- span : Span ,
280
+ if_span : Span ,
281
+ cond_expr : & ' tcx hir:: Expr < ' tcx > ,
277
282
then_expr : & ' tcx hir:: Expr < ' tcx > ,
278
283
coercion : & mut CoerceMany < ' tcx , ' _ , T > ,
279
284
) -> bool
@@ -283,23 +288,88 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
283
288
// If this `if` expr is the parent's function return expr,
284
289
// the cause of the type coercion is the return type, point at it. (#25228)
285
290
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 ) ;
288
293
let mut error = false ;
289
294
coercion. coerce_forced_unit (
290
295
self ,
291
296
& cause,
292
297
|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
297
302
{
298
303
err. span_label ( expr. span , "found here" ) ;
299
304
}
300
305
err. note ( "`if` expressions without `else` evaluate to `()`" ) ;
301
306
err. help ( "consider adding an `else` block that evaluates to the expected type" ) ;
302
307
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
+ }
303
373
} ,
304
374
false ,
305
375
) ;
0 commit comments