@@ -434,10 +434,6 @@ struct DiagCtxtInner {
434
434
/// The delayed bugs and their error guarantees.
435
435
delayed_bugs : Vec < ( DelayedDiagInner , ErrorGuaranteed ) > ,
436
436
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
-
441
437
/// The error count shown to the user at the end.
442
438
deduplicated_err_count : usize ,
443
439
/// The warning count shown to the user at the end.
@@ -475,7 +471,7 @@ struct DiagCtxtInner {
475
471
/// add more information). All stashed diagnostics must be emitted with
476
472
/// `emit_stashed_diagnostics` by the time the `DiagCtxtInner` is dropped,
477
473
/// otherwise an assertion failure will occur.
478
- stashed_diagnostics : FxIndexMap < ( Span , StashKey ) , DiagInner > ,
474
+ stashed_diagnostics : FxIndexMap < ( Span , StashKey ) , ( DiagInner , Option < ErrorGuaranteed > ) > ,
479
475
480
476
future_breakage_diagnostics : Vec < DiagInner > ,
481
477
@@ -559,10 +555,18 @@ pub struct DiagCtxtFlags {
559
555
560
556
impl Drop for DiagCtxtInner {
561
557
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 ( ) ;
565
566
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.
566
570
if self . err_guars . is_empty ( ) {
567
571
self . flush_delayed ( )
568
572
}
@@ -615,7 +619,6 @@ impl DiagCtxt {
615
619
err_guars : Vec :: new ( ) ,
616
620
lint_err_guars : Vec :: new ( ) ,
617
621
delayed_bugs : Vec :: new ( ) ,
618
- stashed_err_count : 0 ,
619
622
deduplicated_err_count : 0 ,
620
623
deduplicated_warn_count : 0 ,
621
624
emitter,
@@ -676,7 +679,6 @@ impl DiagCtxt {
676
679
err_guars,
677
680
lint_err_guars,
678
681
delayed_bugs,
679
- stashed_err_count,
680
682
deduplicated_err_count,
681
683
deduplicated_warn_count,
682
684
emitter : _,
@@ -699,7 +701,6 @@ impl DiagCtxt {
699
701
* err_guars = Default :: default ( ) ;
700
702
* lint_err_guars = Default :: default ( ) ;
701
703
* delayed_bugs = Default :: default ( ) ;
702
- * stashed_err_count = 0 ;
703
704
* deduplicated_err_count = 0 ;
704
705
* deduplicated_warn_count = 0 ;
705
706
* must_produce_diag = false ;
@@ -715,39 +716,111 @@ impl DiagCtxt {
715
716
* fulfilled_expectations = Default :: default ( ) ;
716
717
}
717
718
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
+ } ;
730
750
731
751
// FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
732
752
// if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
733
753
// 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
735
758
}
736
759
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 < ' _ , ( ) > > {
740
764
let key = ( span. with_parent ( None ) , key) ;
741
765
// 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( ) ) ;
748
769
Some ( Diag :: new_diagnostic ( self , diag) )
749
770
}
750
771
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
+
751
824
pub fn has_stashed_diagnostic ( & self , span : Span , key : StashKey ) -> bool {
752
825
self . inner . borrow ( ) . stashed_diagnostics . get ( & ( span. with_parent ( None ) , key) ) . is_some ( )
753
826
}
@@ -757,41 +830,40 @@ impl DiagCtxt {
757
830
self . inner . borrow_mut ( ) . emit_stashed_diagnostics ( )
758
831
}
759
832
760
- /// This excludes lint errors, delayed bugs and stashed errors .
833
+ /// This excludes lint errors, and delayed bugs .
761
834
#[ inline]
762
835
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 ( )
764
843
}
765
844
766
- /// This excludes delayed bugs and stashed errors .
845
+ /// This excludes delayed bugs.
767
846
#[ inline]
768
847
pub fn err_count ( & self ) -> usize {
769
848
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 ( )
780
852
}
781
853
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.
784
856
pub fn has_errors_excluding_lint_errors ( & self ) -> Option < ErrorGuaranteed > {
785
857
self . inner . borrow ( ) . has_errors_excluding_lint_errors ( )
786
858
}
787
859
788
- /// This excludes delayed bugs and stashed errors .
860
+ /// This excludes delayed bugs.
789
861
pub fn has_errors ( & self ) -> Option < ErrorGuaranteed > {
790
862
self . inner . borrow ( ) . has_errors ( )
791
863
}
792
864
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.
795
867
pub fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
796
868
self . inner . borrow ( ) . has_errors_or_delayed_bugs ( )
797
869
}
@@ -876,10 +948,10 @@ impl DiagCtxt {
876
948
}
877
949
}
878
950
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.
883
955
pub fn abort_if_errors ( & self ) {
884
956
if self . has_errors ( ) . is_some ( ) {
885
957
FatalError . raise ( ) ;
@@ -963,7 +1035,7 @@ impl DiagCtxt {
963
1035
inner
964
1036
. stashed_diagnostics
965
1037
. 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) ) ;
967
1039
inner
968
1040
. future_breakage_diagnostics
969
1041
. iter_mut ( )
@@ -1270,12 +1342,8 @@ impl DiagCtxtInner {
1270
1342
fn emit_stashed_diagnostics ( & mut self ) -> Option < ErrorGuaranteed > {
1271
1343
let mut guar = None ;
1272
1344
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 ( ) {
1279
1347
// Unless they're forced, don't flush stashed warnings when
1280
1348
// there are errors, to avoid causing warning overload. The
1281
1349
// stash would've been stolen already if it were important.
@@ -1334,7 +1402,8 @@ impl DiagCtxtInner {
1334
1402
} else {
1335
1403
let backtrace = std:: backtrace:: Backtrace :: capture ( ) ;
1336
1404
// 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`.
1338
1407
#[ allow( deprecated) ]
1339
1408
let guar = ErrorGuaranteed :: unchecked_error_guaranteed ( ) ;
1340
1409
self . delayed_bugs
@@ -1446,11 +1515,31 @@ impl DiagCtxtInner {
1446
1515
}
1447
1516
1448
1517
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
+ } )
1450
1529
}
1451
1530
1452
1531
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
+ )
1454
1543
}
1455
1544
1456
1545
fn has_errors_or_delayed_bugs ( & self ) -> Option < ErrorGuaranteed > {
0 commit comments