@@ -2,9 +2,12 @@ use either::Either;
22use rustc_const_eval:: util:: CallKind ;
33use rustc_data_structures:: captures:: Captures ;
44use 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+ } ;
68use rustc_hir as hir;
79use rustc_hir:: def_id:: DefId ;
10+ use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
811use rustc_hir:: { AsyncGeneratorKind , GeneratorKind } ;
912use rustc_infer:: infer:: TyCtxtInferExt ;
1013use rustc_infer:: traits:: ObligationCause ;
@@ -18,6 +21,7 @@ use rustc_middle::ty::{
1821 self , subst:: Subst , suggest_constraining_type_params, EarlyBinder , PredicateKind , Ty ,
1922} ;
2023use rustc_mir_dataflow:: move_paths:: { InitKind , MoveOutIndex , MovePathIndex } ;
24+ use rustc_span:: hygiene:: DesugaringKind ;
2125use rustc_span:: symbol:: sym;
2226use rustc_span:: { BytePos , Span } ;
2327use rustc_trait_selection:: infer:: InferCtxtExt ;
@@ -94,32 +98,20 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
9498 return ;
9599 }
96100
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,
103106 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,
108108 ) ;
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-
116109 self . buffer_error ( err) ;
117110 } else {
118111 if let Some ( ( reported_place, _) ) = self . has_move_error ( & move_out_indices) {
119112 if self . prefixes ( * reported_place, PrefixSet :: All ) . any ( |p| p == used_place) {
120113 debug ! (
121- "report_use_of_moved_or_uninitialized place: error suppressed \
122- mois={:?}",
114+ "report_use_of_moved_or_uninitialized place: error suppressed mois={:?}" ,
123115 move_out_indices
124116 ) ;
125117 return ;
@@ -326,6 +318,130 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
326318 }
327319 }
328320
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+
329445 fn suggest_borrow_fn_like (
330446 & self ,
331447 err : & mut DiagnosticBuilder < ' tcx , ErrorGuaranteed > ,
@@ -2448,3 +2564,142 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> {
24482564 }
24492565 }
24502566}
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