@@ -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
@@ -559,10 +555,18 @@ pub struct DiagCtxtFlags {
559555
560556impl Drop for DiagCtxtInner {
561557 fn drop ( & mut self ) {
562- // Any stashed diagnostics should have been handled by
563- // `emit_stashed_diagnostics` by now.
564- assert ! ( self . stashed_diagnostics. is_empty( ) ) ;
558+ // For tools using `interface::run_compiler` (e.g. rustc, rustdoc)
559+ // stashed diagnostics will have already been emitted. But for others
560+ // that don't use `interface::run_compiler` (e.g. rustfmt, some clippy
561+ // lints) this fallback is necessary.
562+ //
563+ // Important: it is sound to produce an `ErrorGuaranteed` when stashing
564+ // errors because they are guaranteed to be emitted here or earlier.
565+ self . emit_stashed_diagnostics ( ) ;
565566
567+ // Important: it is sound to produce an `ErrorGuaranteed` when emitting
568+ // delayed bugs because they are guaranteed to be emitted here if
569+ // necessary.
566570 if self . err_guars . is_empty ( ) {
567571 self . flush_delayed ( )
568572 }
@@ -615,7 +619,6 @@ impl DiagCtxt {
615619 err_guars : Vec :: new ( ) ,
616620 lint_err_guars : Vec :: new ( ) ,
617621 delayed_bugs : Vec :: new ( ) ,
618- stashed_err_count : 0 ,
619622 deduplicated_err_count : 0 ,
620623 deduplicated_warn_count : 0 ,
621624 emitter,
@@ -676,7 +679,6 @@ impl DiagCtxt {
676679 err_guars,
677680 lint_err_guars,
678681 delayed_bugs,
679- stashed_err_count,
680682 deduplicated_err_count,
681683 deduplicated_warn_count,
682684 emitter : _,
@@ -699,7 +701,6 @@ impl DiagCtxt {
699701 * err_guars = Default :: default ( ) ;
700702 * lint_err_guars = Default :: default ( ) ;
701703 * delayed_bugs = Default :: default ( ) ;
702- * stashed_err_count = 0 ;
703704 * deduplicated_err_count = 0 ;
704705 * deduplicated_warn_count = 0 ;
705706 * must_produce_diag = false ;
@@ -715,39 +716,111 @@ impl DiagCtxt {
715716 * fulfilled_expectations = Default :: default ( ) ;
716717 }
717718
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- }
719+ /// Stashes a diagnostic for possible later improvement in a different,
720+ /// later stage of the compiler. Possible actions depend on the diagnostic
721+ /// level:
722+ /// - Level::Error: immediately counted as an error that has occurred, because it
723+ /// is guaranteed to be emitted eventually. Can be later accessed with the
724+ /// provided `span` and `key` through
725+ /// [`DiagCtxt::try_steal_modify_and_emit_err`] or
726+ /// [`DiagCtxt::try_steal_replace_and_emit_err`]. These do not allow
727+ /// cancellation or downgrading of the error. Returns
728+ /// `Some(ErrorGuaranteed)`.
729+ /// - Level::Warning and lower (i.e. !is_error()): can be accessed with the
730+ /// provided `span` and `key` through [`DiagCtxt::steal_non_err()`]. This
731+ /// allows cancelling and downgrading of the diagnostic. Returns `None`.
732+ /// - Others: not allowed, will trigger a panic.
733+ pub fn stash_diagnostic (
734+ & self ,
735+ span : Span ,
736+ key : StashKey ,
737+ diag : DiagInner ,
738+ ) -> Option < ErrorGuaranteed > {
739+ let guar = if diag. level ( ) == Level :: Error {
740+ // This `unchecked_error_guaranteed` is valid. It is where the
741+ // `ErrorGuaranteed` for stashed errors originates. See
742+ // `DiagCtxtInner::drop`.
743+ #[ allow( deprecated) ]
744+ Some ( ErrorGuaranteed :: unchecked_error_guaranteed ( ) )
745+ } else if !diag. is_error ( ) {
746+ None
747+ } else {
748+ self . span_bug ( span, format ! ( "invalid level in `stash_diagnostic`: {}" , diag. level) ) ;
749+ } ;
730750
731751 // FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
732752 // if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
733753 // See the PR for a discussion.
734- inner. stashed_diagnostics . insert ( key, diag) ;
754+ let key = ( span. with_parent ( None ) , key) ;
755+ self . inner . borrow_mut ( ) . stashed_diagnostics . insert ( key, ( diag, guar) ) ;
756+
757+ guar
735758 }
736759
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 ( ) ;
760+ /// Steal a previously stashed non-error diagnostic with the given `Span`
761+ /// and [`StashKey`] as the key. Panics if the found diagnostic is an
762+ /// error.
763+ pub fn steal_non_err ( & self , span : Span , key : StashKey ) -> Option < Diag < ' _ , ( ) > > {
740764 let key = ( span. with_parent ( None ) , key) ;
741765 // 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- }
766+ let ( diag, guar) = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ?;
767+ assert ! ( !diag. is_error( ) ) ;
768+ assert ! ( guar. is_none( ) ) ;
748769 Some ( Diag :: new_diagnostic ( self , diag) )
749770 }
750771
772+ /// Steals a previously stashed error with the given `Span` and
773+ /// [`StashKey`] as the key, modifies it, and emits it. Returns `None` if
774+ /// no matching diagnostic is found. Panics if the found diagnostic's level
775+ /// isn't `Level::Error`.
776+ pub fn try_steal_modify_and_emit_err < F > (
777+ & self ,
778+ span : Span ,
779+ key : StashKey ,
780+ mut modify_err : F ,
781+ ) -> Option < ErrorGuaranteed >
782+ where
783+ F : FnMut ( & mut Diag < ' _ > ) ,
784+ {
785+ let key = ( span. with_parent ( None ) , key) ;
786+ // FIXME(#120456) - is `swap_remove` correct?
787+ let err = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ;
788+ err. map ( |( err, guar) | {
789+ // The use of `::<ErrorGuaranteed>` is safe because level is `Level::Error`.
790+ assert_eq ! ( err. level, Level :: Error ) ;
791+ assert ! ( guar. is_some( ) ) ;
792+ let mut err = Diag :: < ErrorGuaranteed > :: new_diagnostic ( self , err) ;
793+ modify_err ( & mut err) ;
794+ assert_eq ! ( err. level, Level :: Error ) ;
795+ err. emit ( )
796+ } )
797+ }
798+
799+ /// Steals a previously stashed error with the given `Span` and
800+ /// [`StashKey`] as the key, cancels it if found, and emits `new_err`.
801+ /// Panics if the found diagnostic's level isn't `Level::Error`.
802+ pub fn try_steal_replace_and_emit_err (
803+ & self ,
804+ span : Span ,
805+ key : StashKey ,
806+ new_err : Diag < ' _ > ,
807+ ) -> ErrorGuaranteed {
808+ let key = ( span. with_parent ( None ) , key) ;
809+ // FIXME(#120456) - is `swap_remove` correct?
810+ let old_err = self . inner . borrow_mut ( ) . stashed_diagnostics . swap_remove ( & key) ;
811+ match old_err {
812+ Some ( ( old_err, guar) ) => {
813+ assert_eq ! ( old_err. level, Level :: Error ) ;
814+ assert ! ( guar. is_some( ) ) ;
815+ // Because `old_err` has already been counted, it can only be
816+ // safely cancelled because the `new_err` supplants it.
817+ Diag :: < ErrorGuaranteed > :: new_diagnostic ( self , old_err) . cancel ( ) ;
818+ }
819+ None => { }
820+ } ;
821+ new_err. emit ( )
822+ }
823+
751824 pub fn has_stashed_diagnostic ( & self , span : Span , key : StashKey ) -> bool {
752825 self . inner . borrow ( ) . stashed_diagnostics . get ( & ( span. with_parent ( None ) , key) ) . is_some ( )
753826 }
@@ -757,41 +830,40 @@ impl DiagCtxt {
757830 self . inner . borrow_mut ( ) . emit_stashed_diagnostics ( )
758831 }
759832
760- /// This excludes lint errors, delayed bugs and stashed errors .
833+ /// This excludes lint errors, and delayed bugs .
761834 #[ inline]
762835 pub fn err_count_excluding_lint_errs ( & self ) -> usize {
763- self . inner . borrow ( ) . err_guars . len ( )
836+ let inner = self . inner . borrow ( ) ;
837+ inner. err_guars . len ( )
838+ + inner
839+ . stashed_diagnostics
840+ . values ( )
841+ . filter ( |( diag, guar) | guar. is_some ( ) && diag. is_lint . is_none ( ) )
842+ . count ( )
764843 }
765844
766- /// This excludes delayed bugs and stashed errors .
845+ /// This excludes delayed bugs.
767846 #[ inline]
768847 pub fn err_count ( & self ) -> usize {
769848 let inner = self . inner . borrow ( ) ;
770- inner. err_guars . len ( ) + inner. lint_err_guars . len ( )
771- }
772-
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
849+ inner. err_guars . len ( )
850+ + inner. lint_err_guars . len ( )
851+ + inner. stashed_diagnostics . values ( ) . filter ( |( _diag, guar) | guar. is_some ( ) ) . count ( )
780852 }
781853
782- /// This excludes lint errors, delayed bugs, and stashed errors . Unless
783- /// absolutely necessary, prefer `has_errors` to this method.
854+ /// This excludes lint errors and delayed bugs . Unless absolutely
855+ /// necessary, prefer `has_errors` to this method.
784856 pub fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
785857 self . inner . borrow ( ) . has_errors_excluding_lint_errors ( )
786858 }
787859
788- /// This excludes delayed bugs and stashed errors .
860+ /// This excludes delayed bugs.
789861 pub fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
790862 self . inner . borrow ( ) . has_errors ( )
791863 }
792864
793- /// This excludes stashed errors . Unless absolutely necessary, prefer
794- /// `has_errors` to this method.
865+ /// This excludes nothing . Unless absolutely necessary, prefer `has_errors`
866+ /// to this method.
795867 pub fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
796868 self . inner . borrow ( ) . has_errors_or_delayed_bugs ( )
797869 }
@@ -876,10 +948,10 @@ impl DiagCtxt {
876948 }
877949 }
878950
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.
951+ /// This excludes delayed bugs. Used for early aborts after errors occurred
952+ /// -- e.g. because continuing in the face of errors is likely to lead to
953+ /// bad results, such as spurious/uninteresting additional errors -- when
954+ /// returning an error `Result` is difficult.
883955 pub fn abort_if_errors ( & self ) {
884956 if self . has_errors ( ) . is_some ( ) {
885957 FatalError . raise ( ) ;
@@ -963,7 +1035,7 @@ impl DiagCtxt {
9631035 inner
9641036 . stashed_diagnostics
9651037 . values_mut ( )
966- . for_each ( |diag| diag. update_unstable_expectation_id ( unstable_to_stable) ) ;
1038+ . for_each ( |( diag, _guar ) | diag. update_unstable_expectation_id ( unstable_to_stable) ) ;
9671039 inner
9681040 . future_breakage_diagnostics
9691041 . iter_mut ( )
@@ -1270,12 +1342,8 @@ impl DiagCtxtInner {
12701342 fn emit_stashed_diagnostics ( & mut self ) -> Option < ErrorGuaranteed > {
12711343 let mut guar = None ;
12721344 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 {
1345+ for ( _, ( diag, _guar) ) in std:: mem:: take ( & mut self . stashed_diagnostics ) . into_iter ( ) {
1346+ if !diag. is_error ( ) {
12791347 // Unless they're forced, don't flush stashed warnings when
12801348 // there are errors, to avoid causing warning overload. The
12811349 // stash would've been stolen already if it were important.
@@ -1334,7 +1402,8 @@ impl DiagCtxtInner {
13341402 } else {
13351403 let backtrace = std:: backtrace:: Backtrace :: capture ( ) ;
13361404 // This `unchecked_error_guaranteed` is valid. It is where the
1337- // `ErrorGuaranteed` for delayed bugs originates.
1405+ // `ErrorGuaranteed` for delayed bugs originates. See
1406+ // `DiagCtxtInner::drop`.
13381407 #[ allow( deprecated) ]
13391408 let guar = ErrorGuaranteed :: unchecked_error_guaranteed ( ) ;
13401409 self . delayed_bugs
@@ -1446,11 +1515,31 @@ impl DiagCtxtInner {
14461515 }
14471516
14481517 fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
1449- self . err_guars . get ( 0 ) . copied ( )
1518+ self . err_guars . get ( 0 ) . copied ( ) . or_else ( || {
1519+ if let Some ( ( _diag, guar) ) = self
1520+ . stashed_diagnostics
1521+ . values ( )
1522+ . find ( |( diag, guar) | guar. is_some ( ) && diag. is_lint . is_none ( ) )
1523+ {
1524+ * guar
1525+ } else {
1526+ None
1527+ }
1528+ } )
14501529 }
14511530
14521531 fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
1453- self . has_errors_excluding_lint_errors ( ) . or_else ( || self . lint_err_guars . get ( 0 ) . copied ( ) )
1532+ self . err_guars . get ( 0 ) . copied ( ) . or_else ( || self . lint_err_guars . get ( 0 ) . copied ( ) ) . or_else (
1533+ || {
1534+ if let Some ( ( _diag, guar) ) =
1535+ self . stashed_diagnostics . values ( ) . find ( |( _diag, guar) | guar. is_some ( ) )
1536+ {
1537+ * guar
1538+ } else {
1539+ None
1540+ }
1541+ } ,
1542+ )
14541543 }
14551544
14561545 fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
0 commit comments