@@ -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 ;
@@ -94,32 +97,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
9497 return ;
9598 }
9699
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) ;
116102 self . buffer_error ( err) ;
117103 } else {
118104 if let Some ( ( reported_place, _) ) = self . has_move_error ( & move_out_indices) {
119105 if self . prefixes ( * reported_place, PrefixSet :: All ) . any ( |p| p == used_place) {
120106 debug ! (
121- "report_use_of_moved_or_uninitialized place: error suppressed \
122- mois={:?}",
107+ "report_use_of_moved_or_uninitialized place: error suppressed mois={:?}" ,
123108 move_out_indices
124109 ) ;
125110 return ;
@@ -326,6 +311,99 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
326311 }
327312 }
328313
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+
329407 fn suggest_borrow_fn_like (
330408 & self ,
331409 err : & mut DiagnosticBuilder < ' tcx , ErrorGuaranteed > ,
@@ -2448,3 +2526,103 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> {
24482526 }
24492527 }
24502528}
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