@@ -435,10 +435,6 @@ struct DiagCtxtInner {
435435 /// The delayed bugs and their error guarantees.
436436 delayed_bugs : Vec < ( DelayedDiagnostic , ErrorGuaranteed ) > ,
437437
438- /// The number of stashed errors. Unlike the other counts, this can go up
439- /// and down, so it doesn't guarantee anything.
440- stashed_err_count : usize ,
441-
442438 /// The error count shown to the user at the end.
443439 deduplicated_err_count : usize ,
444440 /// The warning count shown to the user at the end.
@@ -476,7 +472,7 @@ struct DiagCtxtInner {
476472 /// add more information). All stashed diagnostics must be emitted with
477473 /// `emit_stashed_diagnostics` by the time the `DiagCtxtInner` is dropped,
478474 /// otherwise an assertion failure will occur.
479- stashed_diagnostics : FxIndexMap < ( Span , StashKey ) , Diagnostic > ,
475+ stashed_diagnostics : FxIndexMap < ( Span , StashKey ) , ( Diagnostic , Option < ErrorGuaranteed > ) > ,
480476
481477 future_breakage_diagnostics : Vec < Diagnostic > ,
482478
@@ -562,8 +558,14 @@ impl Drop for DiagCtxtInner {
562558 fn drop ( & mut self ) {
563559 // Any stashed diagnostics should have been handled by
564560 // `emit_stashed_diagnostics` by now.
561+ //
562+ // Important: it is sound to produce an `ErrorGuaranteed` when stashing
563+ // errors because they are guaranteed to have been emitted by here.
565564 assert ! ( self . stashed_diagnostics. is_empty( ) ) ;
566565
566+ // Important: it is sound to produce an `ErrorGuaranteed` when emitting
567+ // delayed bugs because they are guaranteed to be emitted here if
568+ // necessary.
567569 if self . err_guars . is_empty ( ) {
568570 self . flush_delayed ( )
569571 }
@@ -616,7 +618,6 @@ impl DiagCtxt {
616618 err_guars : Vec :: new ( ) ,
617619 lint_err_guars : Vec :: new ( ) ,
618620 delayed_bugs : Vec :: new ( ) ,
619- stashed_err_count : 0 ,
620621 deduplicated_err_count : 0 ,
621622 deduplicated_warn_count : 0 ,
622623 emitter,
@@ -677,7 +678,6 @@ impl DiagCtxt {
677678 err_guars,
678679 lint_err_guars,
679680 delayed_bugs,
680- stashed_err_count,
681681 deduplicated_err_count,
682682 deduplicated_warn_count,
683683 emitter : _,
@@ -700,7 +700,6 @@ impl DiagCtxt {
700700 * err_guars = Default :: default ( ) ;
701701 * lint_err_guars = Default :: default ( ) ;
702702 * delayed_bugs = Default :: default ( ) ;
703- * stashed_err_count = 0 ;
704703 * deduplicated_err_count = 0 ;
705704 * deduplicated_warn_count = 0 ;
706705 * must_produce_diag = false ;
@@ -716,39 +715,111 @@ impl DiagCtxt {
716715 * fulfilled_expectations = Default :: default ( ) ;
717716 }
718717
719- /// Stash a given diagnostic with the given `Span` and [`StashKey`] as the key.
720- /// Retrieve a stashed diagnostic with `steal_diagnostic`.
721- pub fn stash_diagnostic ( & self , span : Span , key : StashKey , diag : Diagnostic ) {
722- let mut inner = self . inner . borrow_mut ( ) ;
723-
724- let key = ( span. with_parent ( None ) , key) ;
725-
726- if diag. is_error ( ) {
727- if diag. is_lint . is_none ( ) {
728- inner. stashed_err_count += 1 ;
729- }
730- }
718+ /// Stashes a diagnostic for possible later improvement in a different,
719+ /// later stage of the compiler. Possible actions depend on the diagnostic
720+ /// level:
721+ /// - Level::Error: immediately counted as an error that has occurred, because it
722+ /// is guaranteed to be emitted eventually. Can be later accessed with the
723+ /// provided `span` and `key` through
724+ /// [`DiagCtxt::try_steal_modify_and_emit_err`] or
725+ /// [`DiagCtxt::try_steal_replace_and_emit_err`]. These do not allow
726+ /// cancellation or downgrading of the error. Returns
727+ /// `Some(ErrorGuaranteed)`.
728+ /// - Level::Warning and lower (i.e. !is_error()): can be accessed with the
729+ /// provided `span` and `key` through [`DiagCtxt::steal_non_err()`]. This
730+ /// allows cancelling and downgrading of the diagnostic. Returns `None`.
731+ /// - Others: not allowed, will trigger a panic.
732+ pub fn stash_diagnostic (
733+ & self ,
734+ span : Span ,
735+ key : StashKey ,
736+ diag : Diagnostic ,
737+ ) -> Option < ErrorGuaranteed > {
738+ let guar = if diag. level ( ) == Level :: Error {
739+ // This `unchecked_error_guaranteed` is valid. It is where the
740+ // `ErrorGuaranteed` for stashed errors originates. See
741+ // `DiagCtxtInner::drop`.
742+ #[ allow( deprecated) ]
743+ Some ( ErrorGuaranteed :: unchecked_error_guaranteed ( ) )
744+ } else if !diag. is_error ( ) {
745+ None
746+ } else {
747+ self . span_bug ( span, format ! ( "invalid level in `stash_diagnostic`: {}" , diag. level) ) ;
748+ } ;
731749
732750 // FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
733751 // if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
734752 // See the PR for a discussion.
735- inner. stashed_diagnostics . insert ( key, diag) ;
753+ let key = ( span. with_parent ( None ) , key) ;
754+ self . inner . borrow_mut ( ) . stashed_diagnostics . insert ( key, ( diag, guar) ) ;
755+
756+ guar
736757 }
737758
738- /// Steal a previously stashed diagnostic with the given `Span` and [`StashKey`] as the key.
739- pub fn steal_diagnostic ( & self , span : Span , key : StashKey ) -> Option < DiagnosticBuilder < ' _ , ( ) > > {
740- let mut inner = self . inner . borrow_mut ( ) ;
759+ /// Steal a previously stashed non-error diagnostic with the given `Span`
760+ /// and [`StashKey`] as the key. Panics if the found diagnostic is an
761+ /// error.
762+ pub fn steal_non_err ( & self , span : Span , key : StashKey ) -> Option < DiagnosticBuilder < ' _ , ( ) > > {
741763 let key = ( span. with_parent ( None ) , key) ;
742764 // FIXME(#120456) - is `swap_remove` correct?
743- let diag = inner. stashed_diagnostics . swap_remove ( & key) ?;
744- if diag. is_error ( ) {
745- if diag. is_lint . is_none ( ) {
746- inner. stashed_err_count -= 1 ;
747- }
748- }
765+ let ( diag, guar) = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ?;
766+ assert ! ( !diag. is_error( ) ) ;
767+ assert ! ( guar. is_none( ) ) ;
749768 Some ( DiagnosticBuilder :: new_diagnostic ( self , diag) )
750769 }
751770
771+ /// Steals a previously stashed error with the given `Span` and
772+ /// [`StashKey`] as the key, modifies it, and emits it. Returns `None` if
773+ /// no matching diagnostic is found. Panics if the found diagnostic's level
774+ /// isn't `Level::Error`.
775+ pub fn try_steal_modify_and_emit_err < F > (
776+ & self ,
777+ span : Span ,
778+ key : StashKey ,
779+ mut modify_err : F ,
780+ ) -> Option < ErrorGuaranteed >
781+ where
782+ F : FnMut ( & mut DiagnosticBuilder < ' _ > ) ,
783+ {
784+ let key = ( span. with_parent ( None ) , key) ;
785+ // FIXME(#120456) - is `swap_remove` correct?
786+ let err = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ;
787+ err. map ( |( err, guar) | {
788+ // The use of `::<ErrorGuaranteed>` is safe because level is `Level::Error`.
789+ assert_eq ! ( err. level, Level :: Error ) ;
790+ assert ! ( guar. is_some( ) ) ;
791+ let mut err = DiagnosticBuilder :: < ErrorGuaranteed > :: new_diagnostic ( self , err) ;
792+ modify_err ( & mut err) ;
793+ assert_eq ! ( err. level, Level :: Error ) ;
794+ err. emit ( )
795+ } )
796+ }
797+
798+ /// Steals a previously stashed error with the given `Span` and
799+ /// [`StashKey`] as the key, cancels it if found, and emits `new_err`.
800+ /// Panics if the found diagnostic's level isn't `Level::Error`.
801+ pub fn try_steal_replace_and_emit_err (
802+ & self ,
803+ span : Span ,
804+ key : StashKey ,
805+ new_err : DiagnosticBuilder < ' _ > ,
806+ ) -> ErrorGuaranteed {
807+ let key = ( span. with_parent ( None ) , key) ;
808+ // FIXME(#120456) - is `swap_remove` correct?
809+ let old_err = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ;
810+ match old_err {
811+ Some ( ( old_err, guar) ) => {
812+ assert_eq ! ( old_err. level, Level :: Error ) ;
813+ assert ! ( guar. is_some( ) ) ;
814+ // Because `old_err` has already been counted, it can only be
815+ // safely cancelled because the `new_err` supplants it.
816+ DiagnosticBuilder :: < ErrorGuaranteed > :: new_diagnostic ( self , old_err) . cancel ( ) ;
817+ }
818+ None => { }
819+ } ;
820+ new_err. emit ( )
821+ }
822+
752823 pub fn has_stashed_diagnostic ( & self , span : Span , key : StashKey ) -> bool {
753824 self . inner . borrow ( ) . stashed_diagnostics . get ( & ( span. with_parent ( None ) , key) ) . is_some ( )
754825 }
@@ -758,41 +829,40 @@ impl DiagCtxt {
758829 self . inner . borrow_mut ( ) . emit_stashed_diagnostics ( )
759830 }
760831
761- /// This excludes lint errors, delayed bugs and stashed errors .
832+ /// This excludes lint errors, and delayed bugs .
762833 #[ inline]
763834 pub fn err_count_excluding_lint_errs ( & self ) -> usize {
764- self . inner . borrow ( ) . err_guars . len ( )
835+ let inner = self . inner . borrow ( ) ;
836+ inner. err_guars . len ( )
837+ + inner
838+ . stashed_diagnostics
839+ . values ( )
840+ . filter ( |( diag, guar) | guar. is_some ( ) && diag. is_lint . is_none ( ) )
841+ . count ( )
765842 }
766843
767- /// This excludes delayed bugs and stashed errors .
844+ /// This excludes delayed bugs.
768845 #[ inline]
769846 pub fn err_count ( & self ) -> usize {
770847 let inner = self . inner . borrow ( ) ;
771- inner. err_guars . len ( ) + inner. lint_err_guars . len ( )
848+ inner. err_guars . len ( )
849+ + inner. lint_err_guars . len ( )
850+ + inner. stashed_diagnostics . values ( ) . filter ( |( _diag, guar) | guar. is_some ( ) ) . count ( )
772851 }
773852
774- /// This excludes normal errors, lint errors, and delayed bugs. Unless
775- /// absolutely necessary, avoid using this. It's dubious because stashed
776- /// errors can later be cancelled, so the presence of a stashed error at
777- /// some point of time doesn't guarantee anything -- there are no
778- /// `ErrorGuaranteed`s here.
779- pub fn stashed_err_count ( & self ) -> usize {
780- self . inner . borrow ( ) . stashed_err_count
781- }
782-
783- /// This excludes lint errors, delayed bugs, and stashed errors. Unless
784- /// absolutely necessary, prefer `has_errors` to this method.
853+ /// This excludes lint errors and delayed bugs. Unless absolutely
854+ /// necessary, prefer `has_errors` to this method.
785855 pub fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
786856 self . inner . borrow ( ) . has_errors_excluding_lint_errors ( )
787857 }
788858
789- /// This excludes delayed bugs and stashed errors .
859+ /// This excludes delayed bugs.
790860 pub fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
791861 self . inner . borrow ( ) . has_errors ( )
792862 }
793863
794- /// This excludes stashed errors . Unless absolutely necessary, prefer
795- /// `has_errors` to this method.
864+ /// This excludes nothing . Unless absolutely necessary, prefer `has_errors`
865+ /// to this method.
796866 pub fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
797867 self . inner . borrow ( ) . has_errors_or_delayed_bugs ( )
798868 }
@@ -877,10 +947,10 @@ impl DiagCtxt {
877947 }
878948 }
879949
880- /// This excludes delayed bugs and stashed errors . Used for early aborts
881- /// after errors occurred -- e.g. because continuing in the face of errors is
882- /// likely to lead to bad results, such as spurious/uninteresting
883- /// additional errors -- when returning an error `Result` is difficult.
950+ /// This excludes delayed bugs. Used for early aborts after errors occurred
951+ /// -- e.g. because continuing in the face of errors is likely to lead to
952+ /// bad results, such as spurious/uninteresting additional errors -- when
953+ /// returning an error `Result` is difficult.
884954 pub fn abort_if_errors ( & self ) {
885955 if self . has_errors ( ) . is_some ( ) {
886956 FatalError . raise ( ) ;
@@ -964,7 +1034,7 @@ impl DiagCtxt {
9641034 inner
9651035 . stashed_diagnostics
9661036 . values_mut ( )
967- . for_each ( |diag| diag. update_unstable_expectation_id ( unstable_to_stable) ) ;
1037+ . for_each ( |( diag, _guar ) | diag. update_unstable_expectation_id ( unstable_to_stable) ) ;
9681038 inner
9691039 . future_breakage_diagnostics
9701040 . iter_mut ( )
@@ -1286,12 +1356,8 @@ impl DiagCtxtInner {
12861356 fn emit_stashed_diagnostics ( & mut self ) -> Option < ErrorGuaranteed > {
12871357 let mut guar = None ;
12881358 let has_errors = !self . err_guars . is_empty ( ) ;
1289- for ( _, diag) in std:: mem:: take ( & mut self . stashed_diagnostics ) . into_iter ( ) {
1290- if diag. is_error ( ) {
1291- if diag. is_lint . is_none ( ) {
1292- self . stashed_err_count -= 1 ;
1293- }
1294- } else {
1359+ for ( _, ( diag, _guar) ) in std:: mem:: take ( & mut self . stashed_diagnostics ) . into_iter ( ) {
1360+ if !diag. is_error ( ) {
12951361 // Unless they're forced, don't flush stashed warnings when
12961362 // there are errors, to avoid causing warning overload. The
12971363 // stash would've been stolen already if it were important.
@@ -1350,7 +1416,8 @@ impl DiagCtxtInner {
13501416 } else {
13511417 let backtrace = std:: backtrace:: Backtrace :: capture ( ) ;
13521418 // This `unchecked_error_guaranteed` is valid. It is where the
1353- // `ErrorGuaranteed` for delayed bugs originates.
1419+ // `ErrorGuaranteed` for delayed bugs originates. See
1420+ // `DiagCtxtInner::drop`.
13541421 #[ allow( deprecated) ]
13551422 let guar = ErrorGuaranteed :: unchecked_error_guaranteed ( ) ;
13561423 self . delayed_bugs
@@ -1462,11 +1529,31 @@ impl DiagCtxtInner {
14621529 }
14631530
14641531 fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
1465- self . err_guars . get ( 0 ) . copied ( )
1532+ self . err_guars . get ( 0 ) . copied ( ) . or_else ( || {
1533+ if let Some ( ( _diag, guar) ) = self
1534+ . stashed_diagnostics
1535+ . values ( )
1536+ . find ( |( diag, guar) | guar. is_some ( ) && diag. is_lint . is_none ( ) )
1537+ {
1538+ * guar
1539+ } else {
1540+ None
1541+ }
1542+ } )
14661543 }
14671544
14681545 fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
1469- self . has_errors_excluding_lint_errors ( ) . or_else ( || self . lint_err_guars . get ( 0 ) . copied ( ) )
1546+ self . err_guars . get ( 0 ) . copied ( ) . or_else ( || self . lint_err_guars . get ( 0 ) . copied ( ) ) . or_else (
1547+ || {
1548+ if let Some ( ( _diag, guar) ) =
1549+ self . stashed_diagnostics . values ( ) . find ( |( _diag, guar) | guar. is_some ( ) )
1550+ {
1551+ * guar
1552+ } else {
1553+ None
1554+ }
1555+ } ,
1556+ )
14701557 }
14711558
14721559 fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
0 commit comments