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,29 +288,106 @@ 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
- |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) ,
304
298
false ,
305
299
) ;
306
300
error
307
301
}
308
302
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
+
309
391
pub fn maybe_get_coercion_reason (
310
392
& self ,
311
393
hir_id : hir:: HirId ,
0 commit comments