@@ -434,10 +434,6 @@ struct DiagCtxtInner {
434434 /// The delayed bugs and their error guarantees.
435435 delayed_bugs : Vec < ( DelayedDiagInner , ErrorGuaranteed ) > ,
436436
437- /// The number of stashed errors. Unlike the other counts, this can go up
438- /// and down, so it doesn't guarantee anything.
439- stashed_err_count : usize ,
440-
441437 /// The error count shown to the user at the end.
442438 deduplicated_err_count : usize ,
443439 /// The warning count shown to the user at the end.
@@ -475,7 +471,7 @@ struct DiagCtxtInner {
475471 /// add more information). All stashed diagnostics must be emitted with
476472 /// `emit_stashed_diagnostics` by the time the `DiagCtxtInner` is dropped,
477473 /// otherwise an assertion failure will occur.
478- stashed_diagnostics : FxIndexMap < ( Span , StashKey ) , DiagInner > ,
474+ stashed_diagnostics : FxIndexMap < ( Span , StashKey ) , ( DiagInner , Option < ErrorGuaranteed > ) > ,
479475
480476 future_breakage_diagnostics : Vec < DiagInner > ,
481477
@@ -561,8 +557,14 @@ impl Drop for DiagCtxtInner {
561557 fn drop ( & mut self ) {
562558 // Any stashed diagnostics should have been handled by
563559 // `emit_stashed_diagnostics` by now.
560+ //
561+ // Important: it is sound to produce an `ErrorGuaranteed` when stashing
562+ // errors because they are guaranteed to have been emitted by here.
564563 assert ! ( self . stashed_diagnostics. is_empty( ) ) ;
565564
565+ // Important: it is sound to produce an `ErrorGuaranteed` when emitting
566+ // delayed bugs because they are guaranteed to be emitted here if
567+ // necessary.
566568 if self . err_guars . is_empty ( ) {
567569 self . flush_delayed ( )
568570 }
@@ -615,7 +617,6 @@ impl DiagCtxt {
615617 err_guars : Vec :: new ( ) ,
616618 lint_err_guars : Vec :: new ( ) ,
617619 delayed_bugs : Vec :: new ( ) ,
618- stashed_err_count : 0 ,
619620 deduplicated_err_count : 0 ,
620621 deduplicated_warn_count : 0 ,
621622 emitter,
@@ -676,7 +677,6 @@ impl DiagCtxt {
676677 err_guars,
677678 lint_err_guars,
678679 delayed_bugs,
679- stashed_err_count,
680680 deduplicated_err_count,
681681 deduplicated_warn_count,
682682 emitter : _,
@@ -699,7 +699,6 @@ impl DiagCtxt {
699699 * err_guars = Default :: default ( ) ;
700700 * lint_err_guars = Default :: default ( ) ;
701701 * delayed_bugs = Default :: default ( ) ;
702- * stashed_err_count = 0 ;
703702 * deduplicated_err_count = 0 ;
704703 * deduplicated_warn_count = 0 ;
705704 * must_produce_diag = false ;
@@ -715,39 +714,111 @@ impl DiagCtxt {
715714 * fulfilled_expectations = Default :: default ( ) ;
716715 }
717716
718- /// Stash a given diagnostic with the given `Span` and [`StashKey`] as the key.
719- /// Retrieve a stashed diagnostic with `steal_diagnostic`.
720- pub fn stash_diagnostic ( & self , span : Span , key : StashKey , diag : DiagInner ) {
721- let mut inner = self . inner . borrow_mut ( ) ;
722-
723- let key = ( span. with_parent ( None ) , key) ;
724-
725- if diag. is_error ( ) {
726- if diag. is_lint . is_none ( ) {
727- inner. stashed_err_count += 1 ;
728- }
729- }
717+ /// Stashes a diagnostic for possible later improvement in a different,
718+ /// later stage of the compiler. Possible actions depend on the diagnostic
719+ /// level:
720+ /// - Level::Error: immediately counted as an error that has occurred, because it
721+ /// is guaranteed to be emitted eventually. Can be later accessed with the
722+ /// provided `span` and `key` through
723+ /// [`DiagCtxt::try_steal_modify_and_emit_err`] or
724+ /// [`DiagCtxt::try_steal_replace_and_emit_err`]. These do not allow
725+ /// cancellation or downgrading of the error. Returns
726+ /// `Some(ErrorGuaranteed)`.
727+ /// - Level::Warning and lower (i.e. !is_error()): can be accessed with the
728+ /// provided `span` and `key` through [`DiagCtxt::steal_non_err()`]. This
729+ /// allows cancelling and downgrading of the diagnostic. Returns `None`.
730+ /// - Others: not allowed, will trigger a panic.
731+ pub fn stash_diagnostic (
732+ & self ,
733+ span : Span ,
734+ key : StashKey ,
735+ diag : DiagInner ,
736+ ) -> Option < ErrorGuaranteed > {
737+ let guar = if diag. level ( ) == Level :: Error {
738+ // This `unchecked_error_guaranteed` is valid. It is where the
739+ // `ErrorGuaranteed` for stashed errors originates. See
740+ // `DiagCtxtInner::drop`.
741+ #[ allow( deprecated) ]
742+ Some ( ErrorGuaranteed :: unchecked_error_guaranteed ( ) )
743+ } else if !diag. is_error ( ) {
744+ None
745+ } else {
746+ self . span_bug ( span, format ! ( "invalid level in `stash_diagnostic`: {}" , diag. level) ) ;
747+ } ;
730748
731749 // FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
732750 // if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
733751 // See the PR for a discussion.
734- inner. stashed_diagnostics . insert ( key, diag) ;
752+ let key = ( span. with_parent ( None ) , key) ;
753+ self . inner . borrow_mut ( ) . stashed_diagnostics . insert ( key, ( diag, guar) ) ;
754+
755+ guar
735756 }
736757
737- /// Steal a previously stashed diagnostic with the given `Span` and [`StashKey`] as the key.
738- pub fn steal_diagnostic ( & self , span : Span , key : StashKey ) -> Option < Diag < ' _ , ( ) > > {
739- let mut inner = self . inner . borrow_mut ( ) ;
758+ /// Steal a previously stashed non-error diagnostic with the given `Span`
759+ /// and [`StashKey`] as the key. Panics if the found diagnostic is an
760+ /// error.
761+ pub fn steal_non_err ( & self , span : Span , key : StashKey ) -> Option < Diag < ' _ , ( ) > > {
740762 let key = ( span. with_parent ( None ) , key) ;
741763 // FIXME(#120456) - is `swap_remove` correct?
742- let diag = inner. stashed_diagnostics . swap_remove ( & key) ?;
743- if diag. is_error ( ) {
744- if diag. is_lint . is_none ( ) {
745- inner. stashed_err_count -= 1 ;
746- }
747- }
764+ let ( diag, guar) = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ?;
765+ assert ! ( !diag. is_error( ) ) ;
766+ assert ! ( guar. is_none( ) ) ;
748767 Some ( Diag :: new_diagnostic ( self , diag) )
749768 }
750769
770+ /// Steals a previously stashed error with the given `Span` and
771+ /// [`StashKey`] as the key, modifies it, and emits it. Returns `None` if
772+ /// no matching diagnostic is found. Panics if the found diagnostic's level
773+ /// isn't `Level::Error`.
774+ pub fn try_steal_modify_and_emit_err < F > (
775+ & self ,
776+ span : Span ,
777+ key : StashKey ,
778+ mut modify_err : F ,
779+ ) -> Option < ErrorGuaranteed >
780+ where
781+ F : FnMut ( & mut Diag < ' _ > ) ,
782+ {
783+ let key = ( span. with_parent ( None ) , key) ;
784+ // FIXME(#120456) - is `swap_remove` correct?
785+ let err = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ;
786+ err. map ( |( err, guar) | {
787+ // The use of `::<ErrorGuaranteed>` is safe because level is `Level::Error`.
788+ assert_eq ! ( err. level, Level :: Error ) ;
789+ assert ! ( guar. is_some( ) ) ;
790+ let mut err = Diag :: < ErrorGuaranteed > :: new_diagnostic ( self , err) ;
791+ modify_err ( & mut err) ;
792+ assert_eq ! ( err. level, Level :: Error ) ;
793+ err. emit ( )
794+ } )
795+ }
796+
797+ /// Steals a previously stashed error with the given `Span` and
798+ /// [`StashKey`] as the key, cancels it if found, and emits `new_err`.
799+ /// Panics if the found diagnostic's level isn't `Level::Error`.
800+ pub fn try_steal_replace_and_emit_err (
801+ & self ,
802+ span : Span ,
803+ key : StashKey ,
804+ new_err : Diag < ' _ > ,
805+ ) -> ErrorGuaranteed {
806+ let key = ( span. with_parent ( None ) , key) ;
807+ // FIXME(#120456) - is `swap_remove` correct?
808+ let old_err = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ;
809+ match old_err {
810+ Some ( ( old_err, guar) ) => {
811+ assert_eq ! ( old_err. level, Level :: Error ) ;
812+ assert ! ( guar. is_some( ) ) ;
813+ // Because `old_err` has already been counted, it can only be
814+ // safely cancelled because the `new_err` supplants it.
815+ Diag :: < ErrorGuaranteed > :: new_diagnostic ( self , old_err) . cancel ( ) ;
816+ }
817+ None => { }
818+ } ;
819+ new_err. emit ( )
820+ }
821+
751822 pub fn has_stashed_diagnostic ( & self , span : Span , key : StashKey ) -> bool {
752823 self . inner . borrow ( ) . stashed_diagnostics . get ( & ( span. with_parent ( None ) , key) ) . is_some ( )
753824 }
@@ -757,41 +828,40 @@ impl DiagCtxt {
757828 self . inner . borrow_mut ( ) . emit_stashed_diagnostics ( )
758829 }
759830
760- /// This excludes lint errors, delayed bugs and stashed errors .
831+ /// This excludes lint errors, and delayed bugs .
761832 #[ inline]
762833 pub fn err_count_excluding_lint_errs ( & self ) -> usize {
763- self . inner . borrow ( ) . err_guars . len ( )
834+ let inner = self . inner . borrow ( ) ;
835+ inner. err_guars . len ( )
836+ + inner
837+ . stashed_diagnostics
838+ . values ( )
839+ . filter ( |( diag, guar) | guar. is_some ( ) && diag. is_lint . is_none ( ) )
840+ . count ( )
764841 }
765842
766- /// This excludes delayed bugs and stashed errors .
843+ /// This excludes delayed bugs.
767844 #[ inline]
768845 pub fn err_count ( & self ) -> usize {
769846 let inner = self . inner . borrow ( ) ;
770- inner. err_guars . len ( ) + inner. lint_err_guars . len ( )
847+ inner. err_guars . len ( )
848+ + inner. lint_err_guars . len ( )
849+ + inner. stashed_diagnostics . values ( ) . filter ( |( _diag, guar) | guar. is_some ( ) ) . count ( )
771850 }
772851
773- /// This excludes normal errors, lint errors, and delayed bugs. Unless
774- /// absolutely necessary, avoid using this. It's dubious because stashed
775- /// errors can later be cancelled, so the presence of a stashed error at
776- /// some point of time doesn't guarantee anything -- there are no
777- /// `ErrorGuaranteed`s here.
778- pub fn stashed_err_count ( & self ) -> usize {
779- self . inner . borrow ( ) . stashed_err_count
780- }
781-
782- /// This excludes lint errors, delayed bugs, and stashed errors. Unless
783- /// absolutely necessary, prefer `has_errors` to this method.
852+ /// This excludes lint errors and delayed bugs. Unless absolutely
853+ /// necessary, prefer `has_errors` to this method.
784854 pub fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
785855 self . inner . borrow ( ) . has_errors_excluding_lint_errors ( )
786856 }
787857
788- /// This excludes delayed bugs and stashed errors .
858+ /// This excludes delayed bugs.
789859 pub fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
790860 self . inner . borrow ( ) . has_errors ( )
791861 }
792862
793- /// This excludes stashed errors . Unless absolutely necessary, prefer
794- /// `has_errors` to this method.
863+ /// This excludes nothing . Unless absolutely necessary, prefer `has_errors`
864+ /// to this method.
795865 pub fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
796866 self . inner . borrow ( ) . has_errors_or_delayed_bugs ( )
797867 }
@@ -876,10 +946,10 @@ impl DiagCtxt {
876946 }
877947 }
878948
879- /// This excludes delayed bugs and stashed errors . Used for early aborts
880- /// after errors occurred -- e.g. because continuing in the face of errors is
881- /// likely to lead to bad results, such as spurious/uninteresting
882- /// additional errors -- when returning an error `Result` is difficult.
949+ /// This excludes delayed bugs. Used for early aborts after errors occurred
950+ /// -- e.g. because continuing in the face of errors is likely to lead to
951+ /// bad results, such as spurious/uninteresting additional errors -- when
952+ /// returning an error `Result` is difficult.
883953 pub fn abort_if_errors ( & self ) {
884954 if self . has_errors ( ) . is_some ( ) {
885955 FatalError . raise ( ) ;
@@ -963,7 +1033,7 @@ impl DiagCtxt {
9631033 inner
9641034 . stashed_diagnostics
9651035 . values_mut ( )
966- . for_each ( |diag| diag. update_unstable_expectation_id ( unstable_to_stable) ) ;
1036+ . for_each ( |( diag, _guar ) | diag. update_unstable_expectation_id ( unstable_to_stable) ) ;
9671037 inner
9681038 . future_breakage_diagnostics
9691039 . iter_mut ( )
@@ -1270,12 +1340,8 @@ impl DiagCtxtInner {
12701340 fn emit_stashed_diagnostics ( & mut self ) -> Option < ErrorGuaranteed > {
12711341 let mut guar = None ;
12721342 let has_errors = !self . err_guars . is_empty ( ) ;
1273- for ( _, diag) in std:: mem:: take ( & mut self . stashed_diagnostics ) . into_iter ( ) {
1274- if diag. is_error ( ) {
1275- if diag. is_lint . is_none ( ) {
1276- self . stashed_err_count -= 1 ;
1277- }
1278- } else {
1343+ for ( _, ( diag, _guar) ) in std:: mem:: take ( & mut self . stashed_diagnostics ) . into_iter ( ) {
1344+ if !diag. is_error ( ) {
12791345 // Unless they're forced, don't flush stashed warnings when
12801346 // there are errors, to avoid causing warning overload. The
12811347 // stash would've been stolen already if it were important.
@@ -1334,7 +1400,8 @@ impl DiagCtxtInner {
13341400 } else {
13351401 let backtrace = std:: backtrace:: Backtrace :: capture ( ) ;
13361402 // This `unchecked_error_guaranteed` is valid. It is where the
1337- // `ErrorGuaranteed` for delayed bugs originates.
1403+ // `ErrorGuaranteed` for delayed bugs originates. See
1404+ // `DiagCtxtInner::drop`.
13381405 #[ allow( deprecated) ]
13391406 let guar = ErrorGuaranteed :: unchecked_error_guaranteed ( ) ;
13401407 self . delayed_bugs
@@ -1446,11 +1513,31 @@ impl DiagCtxtInner {
14461513 }
14471514
14481515 fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
1449- self . err_guars . get ( 0 ) . copied ( )
1516+ self . err_guars . get ( 0 ) . copied ( ) . or_else ( || {
1517+ if let Some ( ( _diag, guar) ) = self
1518+ . stashed_diagnostics
1519+ . values ( )
1520+ . find ( |( diag, guar) | guar. is_some ( ) && diag. is_lint . is_none ( ) )
1521+ {
1522+ * guar
1523+ } else {
1524+ None
1525+ }
1526+ } )
14501527 }
14511528
14521529 fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
1453- self . has_errors_excluding_lint_errors ( ) . or_else ( || self . lint_err_guars . get ( 0 ) . copied ( ) )
1530+ self . err_guars . get ( 0 ) . copied ( ) . or_else ( || self . lint_err_guars . get ( 0 ) . copied ( ) ) . or_else (
1531+ || {
1532+ if let Some ( ( _diag, guar) ) =
1533+ self . stashed_diagnostics . values ( ) . find ( |( _diag, guar) | guar. is_some ( ) )
1534+ {
1535+ * guar
1536+ } else {
1537+ None
1538+ }
1539+ } ,
1540+ )
14541541 }
14551542
14561543 fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
0 commit comments