@@ -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 ;
@@ -94,32 +97,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
94
97
return ;
95
98
}
96
99
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 (
103
- span,
104
- desired_action. as_noun ( ) ,
105
- & self
106
- . describe_place_with_options ( moved_place, IncludingDowncast ( true ) )
107
- . unwrap_or_else ( || "_" . to_owned ( ) ) ,
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
-
100
+ let err =
101
+ self . report_use_of_uninitialized ( mpi, used_place, desired_action, span, use_spans) ;
116
102
self . buffer_error ( err) ;
117
103
} else {
118
104
if let Some ( ( reported_place, _) ) = self . has_move_error ( & move_out_indices) {
119
105
if self . prefixes ( * reported_place, PrefixSet :: All ) . any ( |p| p == used_place) {
120
106
debug ! (
121
- "report_use_of_moved_or_uninitialized place: error suppressed \
122
- mois={:?}",
107
+ "report_use_of_moved_or_uninitialized place: error suppressed mois={:?}" ,
123
108
move_out_indices
124
109
) ;
125
110
return ;
@@ -326,6 +311,99 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
326
311
}
327
312
}
328
313
314
+ fn report_use_of_uninitialized (
315
+ & self ,
316
+ mpi : MovePathIndex ,
317
+ used_place : PlaceRef < ' tcx > ,
318
+ desired_action : InitializationRequiringAction ,
319
+ span : Span ,
320
+ use_spans : UseSpans < ' tcx > ,
321
+ ) -> DiagnosticBuilder < ' cx , ErrorGuaranteed > {
322
+ // We need all statements in the body where the binding was assigned to to later find all
323
+ // the branching code paths where the binding *wasn't* assigned to.
324
+ let inits = & self . move_data . init_path_map [ mpi] ;
325
+ let move_path = & self . move_data . move_paths [ mpi] ;
326
+ let decl_span = self . body . local_decls [ move_path. place . local ] . source_info . span ;
327
+ let mut spans = vec ! [ ] ;
328
+ for init_idx in inits {
329
+ let init = & self . move_data . inits [ * init_idx] ;
330
+ let span = init. span ( & self . body ) ;
331
+ spans. push ( span) ;
332
+ }
333
+
334
+ let ( item_msg, name, desc) =
335
+ match self . describe_place_with_options ( used_place, IncludingDowncast ( true ) ) {
336
+ Some ( name) => ( format ! ( "`{name}`" ) , format ! ( "`{name}`" ) , format ! ( "`{name}` " ) ) ,
337
+ None => ( "value" . to_string ( ) , "the variable" . to_string ( ) , String :: new ( ) ) ,
338
+ } ;
339
+ let initialized = if let InitializationRequiringAction :: PartialAssignment = desired_action {
340
+ // The same error is emitted for bindings that are *sometimes* initialized and the ones
341
+ // that are *partially* initialized by assigning to a field of an uninitialized
342
+ // binding. We differentiate between them for more accurate wording here.
343
+ "fully initialized"
344
+ } else if spans. iter ( ) . filter ( |i| !i. contains ( span) ) . count ( ) == 0 {
345
+ // We filter above to avoid misleading wording in cases like:
346
+ // ```
347
+ // let x;
348
+ // x += 1;
349
+ // ```
350
+ "initialized"
351
+ } else {
352
+ "initialized in all conditions"
353
+ } ;
354
+ let mut err = struct_span_err ! ( self , span, E0381 , "binding {desc}isn't {initialized}" ) ;
355
+ use_spans. var_span_label_path_only (
356
+ & mut err,
357
+ format ! ( "{} occurs due to use{}" , desired_action. as_noun( ) , use_spans. describe( ) ) ,
358
+ ) ;
359
+
360
+ if let InitializationRequiringAction :: PartialAssignment = desired_action {
361
+ err. help (
362
+ "partial initialization isn't supported, fully initialize the binding with \
363
+ a default value and mutate, or use `std::mem::MaybeUninit`",
364
+ ) ;
365
+ }
366
+ let verb = desired_action. as_verb_in_past_tense ( ) ;
367
+ err. span_label ( span, format ! ( "{item_msg} {verb} here but it isn't {initialized}" , ) ) ;
368
+
369
+ // We use the statements were the binding was initialized, and inspect the HIR to look
370
+ // for the branching codepaths that aren't covered, to point at them.
371
+ let hir_id = self . mir_hir_id ( ) ;
372
+ let map = self . infcx . tcx . hir ( ) ;
373
+ let body_id = map. body_owned_by ( hir_id) ;
374
+ let body = map. body ( body_id) ;
375
+
376
+ let mut visitor = ConditionVisitor { spans : & spans, name : & name, errors : vec ! [ ] } ;
377
+ visitor. visit_body ( & body) ;
378
+ if visitor. errors . is_empty ( ) {
379
+ for sp in & spans {
380
+ if * sp < span && !sp. overlaps ( span) {
381
+ err. span_label ( * sp, "binding initialized here in some conditions" ) ;
382
+ }
383
+ }
384
+ }
385
+ for ( sp, label) in visitor. errors {
386
+ if sp < span && !sp. overlaps ( span) {
387
+ // When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention
388
+ // match arms coming after the primary span because they aren't relevant:
389
+ // ```
390
+ // let x;
391
+ // match y {
392
+ // _ if { x = 2; true } => {}
393
+ // _ if {
394
+ // x; //~ ERROR
395
+ // false
396
+ // } => {}
397
+ // _ => {} // We don't want to point to this.
398
+ // };
399
+ // ```
400
+ err. span_label ( sp, & label) ;
401
+ }
402
+ }
403
+ err. span_label ( decl_span, "variable declared here" ) ;
404
+ err
405
+ }
406
+
329
407
fn suggest_borrow_fn_like (
330
408
& self ,
331
409
err : & mut DiagnosticBuilder < ' tcx , ErrorGuaranteed > ,
@@ -2448,3 +2526,103 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> {
2448
2526
}
2449
2527
}
2450
2528
}
2529
+
2530
+ /// Detect whether one of the provided spans is a statement nested within the top-most visited expr
2531
+ struct ReferencedStatementsVisitor < ' a > ( & ' a [ Span ] , bool ) ;
2532
+
2533
+ impl < ' a , ' v > Visitor < ' v > for ReferencedStatementsVisitor < ' a > {
2534
+ fn visit_stmt ( & mut self , s : & ' v hir:: Stmt < ' v > ) {
2535
+ match s. kind {
2536
+ hir:: StmtKind :: Semi ( expr) if self . 0 . contains ( & expr. span ) => {
2537
+ self . 1 = true ;
2538
+ }
2539
+ _ => { }
2540
+ }
2541
+ }
2542
+ }
2543
+
2544
+ /// Given a set of spans representing statements initializing the relevant binding, visit all the
2545
+ /// function expressions looking for branching code paths that *do not* initialize the binding.
2546
+ struct ConditionVisitor < ' b > {
2547
+ spans : & ' b [ Span ] ,
2548
+ name : & ' b str ,
2549
+ errors : Vec < ( Span , String ) > ,
2550
+ }
2551
+
2552
+ impl < ' b , ' v > Visitor < ' v > for ConditionVisitor < ' b > {
2553
+ fn visit_expr ( & mut self , ex : & ' v hir:: Expr < ' v > ) {
2554
+ match ex. kind {
2555
+ hir:: ExprKind :: If ( cond, body, None ) => {
2556
+ // `if` expressions with no `else` that initialize the binding might be missing an
2557
+ // `else` arm.
2558
+ let mut v = ReferencedStatementsVisitor ( self . spans , false ) ;
2559
+ v. visit_expr ( body) ;
2560
+ if v. 1 {
2561
+ self . errors . push ( (
2562
+ cond. span ,
2563
+ format ! (
2564
+ "this `if` expression might be missing an `else` arm where {} is \
2565
+ initialized",
2566
+ self . name,
2567
+ ) ,
2568
+ ) ) ;
2569
+ }
2570
+ }
2571
+ hir:: ExprKind :: If ( cond, body, Some ( other) ) => {
2572
+ // `if` expressions where the binding is only initialized in one of the two arms
2573
+ // might be missing a binding initialization.
2574
+ let mut a = ReferencedStatementsVisitor ( self . spans , false ) ;
2575
+ a. visit_expr ( body) ;
2576
+ let mut b = ReferencedStatementsVisitor ( self . spans , false ) ;
2577
+ b. visit_expr ( other) ;
2578
+ match ( a. 1 , b. 1 ) {
2579
+ ( true , true ) | ( false , false ) => { }
2580
+ ( true , false ) => {
2581
+ self . errors . push ( (
2582
+ cond. span ,
2583
+ format ! ( "{} is uninitialized if this condition isn't met" , self . name, ) ,
2584
+ ) ) ;
2585
+ }
2586
+ ( false , true ) => {
2587
+ self . errors . push ( (
2588
+ cond. span ,
2589
+ format ! ( "{} is uninitialized if this condition is met" , self . name) ,
2590
+ ) ) ;
2591
+ }
2592
+ }
2593
+ }
2594
+ hir:: ExprKind :: Match ( _, arms, _) => {
2595
+ // If the binding is initialized in one of the match arms, then the other match
2596
+ // arms might be missing an initialization.
2597
+ let results: Vec < bool > = arms
2598
+ . iter ( )
2599
+ . map ( |arm| {
2600
+ let mut v = ReferencedStatementsVisitor ( self . spans , false ) ;
2601
+ v. visit_arm ( arm) ;
2602
+ v. 1
2603
+ } )
2604
+ . collect ( ) ;
2605
+ if results. iter ( ) . any ( |x| * x) && !results. iter ( ) . all ( |x| * x) {
2606
+ for ( arm, seen) in arms. iter ( ) . zip ( results) {
2607
+ if !seen {
2608
+ self . errors . push ( (
2609
+ arm. pat . span ,
2610
+ format ! (
2611
+ "{} is uninitialized if this pattern is matched" ,
2612
+ self . name
2613
+ ) ,
2614
+ ) ) ;
2615
+ }
2616
+ }
2617
+ }
2618
+ }
2619
+ // FIXME: should we also account for binops, particularly `&&` and `||`? `try` should
2620
+ // also be accounted for. For now it is fine, as if we don't find *any* relevant
2621
+ // branching code paths, we point at the places where the binding *is* initialized for
2622
+ // *some* context. We should also specialize the output for `while` and `for` loops,
2623
+ // but for now we can rely on their desugaring to provide appropriate output.
2624
+ _ => { }
2625
+ }
2626
+ walk_expr ( self , ex) ;
2627
+ }
2628
+ }
0 commit comments