@@ -2,9 +2,12 @@ use either::Either;
2
2
use rustc_const_eval:: util:: CallKind ;
3
3
use rustc_data_structures:: captures:: Captures ;
4
4
use rustc_data_structures:: fx:: FxHashSet ;
5
- use rustc_errors:: { Applicability , Diagnostic , DiagnosticBuilder , ErrorGuaranteed , MultiSpan } ;
5
+ use rustc_errors:: {
6
+ struct_span_err, Applicability , Diagnostic , DiagnosticBuilder , ErrorGuaranteed , MultiSpan ,
7
+ } ;
6
8
use rustc_hir as hir;
7
9
use rustc_hir:: def_id:: DefId ;
10
+ use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
8
11
use rustc_hir:: { AsyncGeneratorKind , GeneratorKind } ;
9
12
use rustc_infer:: infer:: TyCtxtInferExt ;
10
13
use rustc_infer:: traits:: ObligationCause ;
@@ -18,6 +21,7 @@ use rustc_middle::ty::{
18
21
self , subst:: Subst , suggest_constraining_type_params, EarlyBinder , PredicateKind , Ty ,
19
22
} ;
20
23
use rustc_mir_dataflow:: move_paths:: { InitKind , MoveOutIndex , MovePathIndex } ;
24
+ use rustc_span:: hygiene:: DesugaringKind ;
21
25
use rustc_span:: symbol:: sym;
22
26
use rustc_span:: { BytePos , Span } ;
23
27
use rustc_trait_selection:: infer:: InferCtxtExt ;
@@ -94,32 +98,20 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
94
98
return ;
95
99
}
96
100
97
- let item_msg =
98
- match self . describe_place_with_options ( used_place, IncludingDowncast ( true ) ) {
99
- Some ( name) => format ! ( "`{}`" , name) ,
100
- None => "value" . to_owned ( ) ,
101
- } ;
102
- let mut err = self . cannot_act_on_uninitialized_variable (
101
+ let err = self . report_use_of_uninitialized (
102
+ mpi,
103
+ used_place,
104
+ moved_place,
105
+ desired_action,
103
106
span,
104
- desired_action. as_noun ( ) ,
105
- & self
106
- . describe_place_with_options ( moved_place, IncludingDowncast ( true ) )
107
- . unwrap_or_else ( || "_" . to_owned ( ) ) ,
107
+ use_spans,
108
108
) ;
109
- err. span_label ( span, format ! ( "use of possibly-uninitialized {}" , item_msg) ) ;
110
-
111
- use_spans. var_span_label_path_only (
112
- & mut err,
113
- format ! ( "{} occurs due to use{}" , desired_action. as_noun( ) , use_spans. describe( ) ) ,
114
- ) ;
115
-
116
109
self . buffer_error ( err) ;
117
110
} else {
118
111
if let Some ( ( reported_place, _) ) = self . has_move_error ( & move_out_indices) {
119
112
if self . prefixes ( * reported_place, PrefixSet :: All ) . any ( |p| p == used_place) {
120
113
debug ! (
121
- "report_use_of_moved_or_uninitialized place: error suppressed \
122
- mois={:?}",
114
+ "report_use_of_moved_or_uninitialized place: error suppressed mois={:?}" ,
123
115
move_out_indices
124
116
) ;
125
117
return ;
@@ -326,6 +318,130 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
326
318
}
327
319
}
328
320
321
+ fn report_use_of_uninitialized (
322
+ & self ,
323
+ mpi : MovePathIndex ,
324
+ used_place : PlaceRef < ' tcx > ,
325
+ moved_place : PlaceRef < ' tcx > ,
326
+ desired_action : InitializationRequiringAction ,
327
+ span : Span ,
328
+ use_spans : UseSpans < ' tcx > ,
329
+ ) -> DiagnosticBuilder < ' cx , ErrorGuaranteed > {
330
+ // We need all statements in the body where the binding was assigned to to later find all
331
+ // the branching code paths where the binding *wasn't* assigned to.
332
+ let inits = & self . move_data . init_path_map [ mpi] ;
333
+ let move_path = & self . move_data . move_paths [ mpi] ;
334
+ let decl_span = self . body . local_decls [ move_path. place . local ] . source_info . span ;
335
+ let mut spans = vec ! [ ] ;
336
+ for init_idx in inits {
337
+ let init = & self . move_data . inits [ * init_idx] ;
338
+ let span = init. span ( & self . body ) ;
339
+ if !span. is_dummy ( ) {
340
+ spans. push ( span) ;
341
+ }
342
+ }
343
+
344
+ let ( name, desc) =
345
+ match self . describe_place_with_options ( moved_place, IncludingDowncast ( true ) ) {
346
+ Some ( name) => ( format ! ( "`{name}`" ) , format ! ( "`{name}` " ) ) ,
347
+ None => ( "the variable" . to_string ( ) , String :: new ( ) ) ,
348
+ } ;
349
+ let path = match self . describe_place_with_options ( used_place, IncludingDowncast ( true ) ) {
350
+ Some ( name) => format ! ( "`{name}`" ) ,
351
+ None => "value" . to_string ( ) ,
352
+ } ;
353
+
354
+ // We use the statements were the binding was initialized, and inspect the HIR to look
355
+ // for the branching codepaths that aren't covered, to point at them.
356
+ let hir_id = self . mir_hir_id ( ) ;
357
+ let map = self . infcx . tcx . hir ( ) ;
358
+ let body_id = map. body_owned_by ( hir_id) ;
359
+ let body = map. body ( body_id) ;
360
+
361
+ let mut visitor = ConditionVisitor { spans : & spans, name : & name, errors : vec ! [ ] } ;
362
+ visitor. visit_body ( & body) ;
363
+
364
+ let isnt_initialized = if let InitializationRequiringAction :: PartialAssignment
365
+ | InitializationRequiringAction :: Assignment = desired_action
366
+ {
367
+ // The same error is emitted for bindings that are *sometimes* initialized and the ones
368
+ // that are *partially* initialized by assigning to a field of an uninitialized
369
+ // binding. We differentiate between them for more accurate wording here.
370
+ "isn't fully initialized"
371
+ } else if spans
372
+ . iter ( )
373
+ . filter ( |i| {
374
+ // We filter these to avoid misleading wording in cases like the following,
375
+ // where `x` has an `init`, but it is in the same place we're looking at:
376
+ // ```
377
+ // let x;
378
+ // x += 1;
379
+ // ```
380
+ !i. contains ( span)
381
+ // We filter these to avoid incorrect main message on `match-cfg-fake-edges.rs`
382
+ && !visitor
383
+ . errors
384
+ . iter ( )
385
+ . map ( |( sp, _) | * sp)
386
+ . any ( |sp| span < sp && !sp. contains ( span) )
387
+ } )
388
+ . count ( )
389
+ == 0
390
+ {
391
+ "isn't initialized"
392
+ } else {
393
+ "is possibly-uninitialized"
394
+ } ;
395
+
396
+ let used = desired_action. as_general_verb_in_past_tense ( ) ;
397
+ let mut err =
398
+ struct_span_err ! ( self , span, E0381 , "{used} binding {desc}{isnt_initialized}" ) ;
399
+ use_spans. var_span_label_path_only (
400
+ & mut err,
401
+ format ! ( "{} occurs due to use{}" , desired_action. as_noun( ) , use_spans. describe( ) ) ,
402
+ ) ;
403
+
404
+ if let InitializationRequiringAction :: PartialAssignment
405
+ | InitializationRequiringAction :: Assignment = desired_action
406
+ {
407
+ err. help (
408
+ "partial initialization isn't supported, fully initialize the binding with a \
409
+ default value and mutate it, or use `std::mem::MaybeUninit`",
410
+ ) ;
411
+ }
412
+ err. span_label ( span, format ! ( "{path} {used} here but it {isnt_initialized}" ) ) ;
413
+
414
+ let mut shown = false ;
415
+ for ( sp, label) in visitor. errors {
416
+ if sp < span && !sp. overlaps ( span) {
417
+ // When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention
418
+ // match arms coming after the primary span because they aren't relevant:
419
+ // ```
420
+ // let x;
421
+ // match y {
422
+ // _ if { x = 2; true } => {}
423
+ // _ if {
424
+ // x; //~ ERROR
425
+ // false
426
+ // } => {}
427
+ // _ => {} // We don't want to point to this.
428
+ // };
429
+ // ```
430
+ err. span_label ( sp, & label) ;
431
+ shown = true ;
432
+ }
433
+ }
434
+ if !shown {
435
+ for sp in & spans {
436
+ if * sp < span && !sp. overlaps ( span) {
437
+ err. span_label ( * sp, "binding initialized here in some conditions" ) ;
438
+ }
439
+ }
440
+ }
441
+ err. span_label ( decl_span, "binding declared here but left uninitialized" ) ;
442
+ err
443
+ }
444
+
329
445
fn suggest_borrow_fn_like (
330
446
& self ,
331
447
err : & mut DiagnosticBuilder < ' tcx , ErrorGuaranteed > ,
@@ -2448,3 +2564,142 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> {
2448
2564
}
2449
2565
}
2450
2566
}
2567
+
2568
+ /// Detect whether one of the provided spans is a statement nested within the top-most visited expr
2569
+ struct ReferencedStatementsVisitor < ' a > ( & ' a [ Span ] , bool ) ;
2570
+
2571
+ impl < ' a , ' v > Visitor < ' v > for ReferencedStatementsVisitor < ' a > {
2572
+ fn visit_stmt ( & mut self , s : & ' v hir:: Stmt < ' v > ) {
2573
+ match s. kind {
2574
+ hir:: StmtKind :: Semi ( expr) if self . 0 . contains ( & expr. span ) => {
2575
+ self . 1 = true ;
2576
+ }
2577
+ _ => { }
2578
+ }
2579
+ }
2580
+ }
2581
+
2582
+ /// Given a set of spans representing statements initializing the relevant binding, visit all the
2583
+ /// function expressions looking for branching code paths that *do not* initialize the binding.
2584
+ struct ConditionVisitor < ' b > {
2585
+ spans : & ' b [ Span ] ,
2586
+ name : & ' b str ,
2587
+ errors : Vec < ( Span , String ) > ,
2588
+ }
2589
+
2590
+ impl < ' b , ' v > Visitor < ' v > for ConditionVisitor < ' b > {
2591
+ fn visit_expr ( & mut self , ex : & ' v hir:: Expr < ' v > ) {
2592
+ match ex. kind {
2593
+ hir:: ExprKind :: If ( cond, body, None ) => {
2594
+ // `if` expressions with no `else` that initialize the binding might be missing an
2595
+ // `else` arm.
2596
+ let mut v = ReferencedStatementsVisitor ( self . spans , false ) ;
2597
+ v. visit_expr ( body) ;
2598
+ if v. 1 {
2599
+ self . errors . push ( (
2600
+ cond. span ,
2601
+ format ! (
2602
+ "if this `if` condition is `false`, {} is not initialized" ,
2603
+ self . name,
2604
+ ) ,
2605
+ ) ) ;
2606
+ self . errors . push ( (
2607
+ ex. span . shrink_to_hi ( ) ,
2608
+ format ! ( "an `else` arm might be missing here, initializing {}" , self . name) ,
2609
+ ) ) ;
2610
+ }
2611
+ }
2612
+ hir:: ExprKind :: If ( cond, body, Some ( other) ) => {
2613
+ // `if` expressions where the binding is only initialized in one of the two arms
2614
+ // might be missing a binding initialization.
2615
+ let mut a = ReferencedStatementsVisitor ( self . spans , false ) ;
2616
+ a. visit_expr ( body) ;
2617
+ let mut b = ReferencedStatementsVisitor ( self . spans , false ) ;
2618
+ b. visit_expr ( other) ;
2619
+ match ( a. 1 , b. 1 ) {
2620
+ ( true , true ) | ( false , false ) => { }
2621
+ ( true , false ) => {
2622
+ if other. span . is_desugaring ( DesugaringKind :: WhileLoop ) {
2623
+ self . errors . push ( (
2624
+ cond. span ,
2625
+ format ! (
2626
+ "if this condition isn't met and the `while` loop runs 0 \
2627
+ times, {} is not initialized",
2628
+ self . name
2629
+ ) ,
2630
+ ) ) ;
2631
+ } else {
2632
+ self . errors . push ( (
2633
+ body. span . shrink_to_hi ( ) . until ( other. span ) ,
2634
+ format ! (
2635
+ "if the `if` condition is `false` and this `else` arm is \
2636
+ executed, {} is not initialized",
2637
+ self . name
2638
+ ) ,
2639
+ ) ) ;
2640
+ }
2641
+ }
2642
+ ( false , true ) => {
2643
+ self . errors . push ( (
2644
+ cond. span ,
2645
+ format ! (
2646
+ "if this condition is `true`, {} is not initialized" ,
2647
+ self . name
2648
+ ) ,
2649
+ ) ) ;
2650
+ }
2651
+ }
2652
+ }
2653
+ hir:: ExprKind :: Match ( e, arms, loop_desugar) => {
2654
+ // If the binding is initialized in one of the match arms, then the other match
2655
+ // arms might be missing an initialization.
2656
+ let results: Vec < bool > = arms
2657
+ . iter ( )
2658
+ . map ( |arm| {
2659
+ let mut v = ReferencedStatementsVisitor ( self . spans , false ) ;
2660
+ v. visit_arm ( arm) ;
2661
+ v. 1
2662
+ } )
2663
+ . collect ( ) ;
2664
+ if results. iter ( ) . any ( |x| * x) && !results. iter ( ) . all ( |x| * x) {
2665
+ for ( arm, seen) in arms. iter ( ) . zip ( results) {
2666
+ if !seen {
2667
+ if loop_desugar == hir:: MatchSource :: ForLoopDesugar {
2668
+ self . errors . push ( (
2669
+ e. span ,
2670
+ format ! (
2671
+ "if the `for` loop runs 0 times, {} is not initialized " ,
2672
+ self . name
2673
+ ) ,
2674
+ ) ) ;
2675
+ } else if let Some ( guard) = & arm. guard {
2676
+ self . errors . push ( (
2677
+ arm. pat . span . to ( guard. body ( ) . span ) ,
2678
+ format ! (
2679
+ "if this pattern and condition are matched, {} is not \
2680
+ initialized",
2681
+ self . name
2682
+ ) ,
2683
+ ) ) ;
2684
+ } else {
2685
+ self . errors . push ( (
2686
+ arm. pat . span ,
2687
+ format ! (
2688
+ "if this pattern is matched, {} is not initialized" ,
2689
+ self . name
2690
+ ) ,
2691
+ ) ) ;
2692
+ }
2693
+ }
2694
+ }
2695
+ }
2696
+ }
2697
+ // FIXME: should we also account for binops, particularly `&&` and `||`? `try` should
2698
+ // also be accounted for. For now it is fine, as if we don't find *any* relevant
2699
+ // branching code paths, we point at the places where the binding *is* initialized for
2700
+ // *some* context.
2701
+ _ => { }
2702
+ }
2703
+ walk_expr ( self , ex) ;
2704
+ }
2705
+ }
0 commit comments