From bf62d5913a702754d46a0e9210fcf608deba63af Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 9 Feb 2024 16:16:22 +1100 Subject: [PATCH 1/7] Give `TRACK_DIAGNOSTIC` a return value. This means `DiagCtxtInner::emit_diagnostic` can return its result directly, rather than having to modify a local variable. --- compiler/rustc_errors/src/lib.rs | 24 ++++++++++++----------- compiler/rustc_interface/src/callbacks.rs | 10 +++++----- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 0a533833e64bd..ce24f1ceaac7b 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -526,12 +526,15 @@ pub enum StashKey { UndeterminedMacroResolution, } -fn default_track_diagnostic(diag: DiagInner, f: &mut dyn FnMut(DiagInner)) { +fn default_track_diagnostic(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R { (*f)(diag) } -pub static TRACK_DIAGNOSTIC: AtomicRef = - AtomicRef::new(&(default_track_diagnostic as _)); +/// Diagnostics emitted by `DiagCtxtInner::emit_diagnostic` are passed through this function. Used +/// for tracking by incremental, to replay diagnostics as necessary. +pub static TRACK_DIAGNOSTIC: AtomicRef< + fn(DiagInner, &mut dyn FnMut(DiagInner) -> Option) -> Option, +> = AtomicRef::new(&(default_track_diagnostic as _)); #[derive(Copy, Clone, Default)] pub struct DiagCtxtFlags { @@ -1406,19 +1409,18 @@ impl DiagCtxtInner { } Warning if !self.flags.can_emit_warnings => { if diagnostic.has_future_breakage() { - (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {}); + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); } return None; } Allow | Expect(_) => { - (*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {}); + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); return None; } _ => {} } - let mut guaranteed = None; - (*TRACK_DIAGNOSTIC)(diagnostic, &mut |mut diagnostic| { + TRACK_DIAGNOSTIC(diagnostic, &mut |mut diagnostic| { if let Some(code) = diagnostic.code { self.emitted_diagnostic_codes.insert(code); } @@ -1481,17 +1483,17 @@ impl DiagCtxtInner { // `ErrorGuaranteed` for errors and lint errors originates. #[allow(deprecated)] let guar = ErrorGuaranteed::unchecked_error_guaranteed(); - guaranteed = Some(guar); if is_lint { self.lint_err_guars.push(guar); } else { self.err_guars.push(guar); } self.panic_if_treat_err_as_bug(); + Some(guar) + } else { + None } - }); - - guaranteed + }) } fn treat_err_as_bug(&self) -> bool { diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs index f44ae705a3c27..a27f73789cdef 100644 --- a/compiler/rustc_interface/src/callbacks.rs +++ b/compiler/rustc_interface/src/callbacks.rs @@ -29,7 +29,7 @@ fn track_span_parent(def_id: rustc_span::def_id::LocalDefId) { /// This is a callback from `rustc_errors` as it cannot access the implicit state /// in `rustc_middle` otherwise. It is used when diagnostic messages are /// emitted and stores them in the current query, if there is one. -fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner)) { +fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R { tls::with_context_opt(|icx| { if let Some(icx) = icx { if let Some(diagnostics) = icx.diagnostics { @@ -38,11 +38,11 @@ fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner)) { // Diagnostics are tracked, we can ignore the dependency. let icx = tls::ImplicitCtxt { task_deps: TaskDepsRef::Ignore, ..icx.clone() }; - return tls::enter_context(&icx, move || (*f)(diagnostic)); + tls::enter_context(&icx, move || (*f)(diagnostic)) + } else { + // In any other case, invoke diagnostics anyway. + (*f)(diagnostic) } - - // In any other case, invoke diagnostics anyway. - (*f)(diagnostic); }) } From ecd3718bc0b3f823b96d6949ba22f77cfbeff5c1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Feb 2024 12:20:38 +1100 Subject: [PATCH 2/7] Inline and remove `Level::get_diagnostic_id`. It has a single call site, and this will enable subsequent refactorings. --- compiler/rustc_errors/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index ce24f1ceaac7b..0c8308b6b09b6 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1356,17 +1356,17 @@ impl DiagCtxtInner { fn emit_diagnostic(&mut self, mut diagnostic: DiagInner) -> Option { assert!(diagnostic.level.can_be_top_or_sub().0); - if let Some(expectation_id) = diagnostic.level.get_expectation_id() { + if let Expect(expect_id) | ForceWarning(Some(expect_id)) = diagnostic.level { // The `LintExpectationId` can be stable or unstable depending on when it was created. // Diagnostics created before the definition of `HirId`s are unstable and can not yet // be stored. Instead, they are buffered until the `LintExpectationId` is replaced by // a stable one by the `LintLevelsBuilder`. - if let LintExpectationId::Unstable { .. } = expectation_id { + if let LintExpectationId::Unstable { .. } = expect_id { self.unstable_expect_diagnostics.push(diagnostic); return None; } self.suppressed_expected_diag = true; - self.fulfilled_expectations.insert(expectation_id.normalize()); + self.fulfilled_expectations.insert(expect_id.normalize()); } if diagnostic.has_future_breakage() { @@ -1794,13 +1794,6 @@ impl Level { matches!(*self, FailureNote) } - pub fn get_expectation_id(&self) -> Option { - match self { - Expect(id) | ForceWarning(Some(id)) => Some(*id), - _ => None, - } - } - // Can this level be used in a top-level diagnostic message and/or a // subdiagnostic message? fn can_be_top_or_sub(&self) -> (bool, bool) { From 272e60bd3ef5ea2424b70b0f4ec821f38fa3dc79 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Feb 2024 13:08:24 +1100 Subject: [PATCH 3/7] Move `DelayedBug` handling into the `match`. It results in a tiny bit of duplication (another `self.treat_next_err_as_bug()` condition) but I think it's worth it to get more code into the main `match`. --- compiler/rustc_errors/src/lib.rs | 51 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 0c8308b6b09b6..2280d3a24172d 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1377,35 +1377,40 @@ impl DiagCtxtInner { self.future_breakage_diagnostics.push(diagnostic.clone()); } - // Note that because this comes before the `match` below, - // `-Zeagerly-emit-delayed-bugs` continues to work even after we've - // issued an error and stopped recording new delayed bugs. - if diagnostic.level == DelayedBug && self.flags.eagerly_emit_delayed_bugs { - diagnostic.level = Error; - } - match diagnostic.level { - // This must come after the possible promotion of `DelayedBug` to - // `Error` above. Fatal | Error if self.treat_next_err_as_bug() => { + // `Fatal` and `Error` can be promoted to `Bug`. diagnostic.level = Bug; } DelayedBug => { - // If we have already emitted at least one error, we don't need - // to record the delayed bug, because it'll never be used. - return if let Some(guar) = self.has_errors() { - Some(guar) + // Note that because we check these conditions first, + // `-Zeagerly-emit-delayed-bugs` and `-Ztreat-err-as-bug` + // continue to work even after we've issued an error and + // stopped recording new delayed bugs. + if self.flags.eagerly_emit_delayed_bugs { + // `DelayedBug` can be promoted to `Error` or `Bug`. + if self.treat_next_err_as_bug() { + diagnostic.level = Bug; + } else { + diagnostic.level = Error; + } } else { - let backtrace = std::backtrace::Backtrace::capture(); - // This `unchecked_error_guaranteed` is valid. It is where the - // `ErrorGuaranteed` for delayed bugs originates. See - // `DiagCtxtInner::drop`. - #[allow(deprecated)] - let guar = ErrorGuaranteed::unchecked_error_guaranteed(); - self.delayed_bugs - .push((DelayedDiagInner::with_backtrace(diagnostic, backtrace), guar)); - Some(guar) - }; + // If we have already emitted at least one error, we don't need + // to record the delayed bug, because it'll never be used. + return if let Some(guar) = self.has_errors() { + Some(guar) + } else { + let backtrace = std::backtrace::Backtrace::capture(); + // This `unchecked_error_guaranteed` is valid. It is where the + // `ErrorGuaranteed` for delayed bugs originates. See + // `DiagCtxtInner::drop`. + #[allow(deprecated)] + let guar = ErrorGuaranteed::unchecked_error_guaranteed(); + self.delayed_bugs + .push((DelayedDiagInner::with_backtrace(diagnostic, backtrace), guar)); + Some(guar) + }; + } } Warning if !self.flags.can_emit_warnings => { if diagnostic.has_future_breakage() { From c81767e7cb06dacc7bb9a5ec0b746b0c774e0aa1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Feb 2024 13:14:41 +1100 Subject: [PATCH 4/7] Reorder `has_future_breakage` handling. This will enable additional refactorings. --- compiler/rustc_errors/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 2280d3a24172d..e073520870905 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1356,6 +1356,14 @@ impl DiagCtxtInner { fn emit_diagnostic(&mut self, mut diagnostic: DiagInner) -> Option { assert!(diagnostic.level.can_be_top_or_sub().0); + if diagnostic.has_future_breakage() { + // Future breakages aren't emitted if they're Level::Allow, + // but they still need to be constructed and stashed below, + // so they'll trigger the must_produce_diag check. + self.suppressed_expected_diag = true; + self.future_breakage_diagnostics.push(diagnostic.clone()); + } + if let Expect(expect_id) | ForceWarning(Some(expect_id)) = diagnostic.level { // The `LintExpectationId` can be stable or unstable depending on when it was created. // Diagnostics created before the definition of `HirId`s are unstable and can not yet @@ -1369,14 +1377,6 @@ impl DiagCtxtInner { self.fulfilled_expectations.insert(expect_id.normalize()); } - if diagnostic.has_future_breakage() { - // Future breakages aren't emitted if they're Level::Allow, - // but they still need to be constructed and stashed below, - // so they'll trigger the must_produce_diag check. - self.suppressed_expected_diag = true; - self.future_breakage_diagnostics.push(diagnostic.clone()); - } - match diagnostic.level { Fatal | Error if self.treat_next_err_as_bug() => { // `Fatal` and `Error` can be promoted to `Bug`. From aec4bdb0a2802765ee90e4f59c9173f94f28f2eb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Feb 2024 14:20:41 +1100 Subject: [PATCH 5/7] Move `Expect`/`ForceWarning` handling into the `match`. Note that `self.suppressed_expected_diag` is no longer set for `ForceWarning`, which is good. Nor is `TRACK_DIAGNOSTIC` called for `Allow`, which is also good. --- compiler/rustc_errors/src/lib.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index e073520870905..0f3999062913f 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1364,19 +1364,6 @@ impl DiagCtxtInner { self.future_breakage_diagnostics.push(diagnostic.clone()); } - if let Expect(expect_id) | ForceWarning(Some(expect_id)) = diagnostic.level { - // The `LintExpectationId` can be stable or unstable depending on when it was created. - // Diagnostics created before the definition of `HirId`s are unstable and can not yet - // be stored. Instead, they are buffered until the `LintExpectationId` is replaced by - // a stable one by the `LintLevelsBuilder`. - if let LintExpectationId::Unstable { .. } = expect_id { - self.unstable_expect_diagnostics.push(diagnostic); - return None; - } - self.suppressed_expected_diag = true; - self.fulfilled_expectations.insert(expect_id.normalize()); - } - match diagnostic.level { Fatal | Error if self.treat_next_err_as_bug() => { // `Fatal` and `Error` can be promoted to `Bug`. @@ -1418,10 +1405,25 @@ impl DiagCtxtInner { } return None; } - Allow | Expect(_) => { - TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + Allow => { return None; } + Expect(expect_id) | ForceWarning(Some(expect_id)) => { + // Diagnostics created before the definition of `HirId`s are + // unstable and can not yet be stored. Instead, they are + // buffered until the `LintExpectationId` is replaced by a + // stable one by the `LintLevelsBuilder`. + if let LintExpectationId::Unstable { .. } = expect_id { + self.unstable_expect_diagnostics.push(diagnostic); + return None; + } + self.fulfilled_expectations.insert(expect_id.normalize()); + if let Expect(_) = diagnostic.level { + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + self.suppressed_expected_diag = true; + return None; + } + } _ => {} } From a7d926265f25abfd05b13a3a0144f2ad9dd02dd7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Feb 2024 14:25:29 +1100 Subject: [PATCH 6/7] Add comments about `TRACK_DIAGNOSTIC` use. Also add an assertion for the levels allowed with `has_future_breakage`. --- compiler/rustc_errors/src/lib.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 0f3999062913f..738016f9b999c 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1360,10 +1360,13 @@ impl DiagCtxtInner { // Future breakages aren't emitted if they're Level::Allow, // but they still need to be constructed and stashed below, // so they'll trigger the must_produce_diag check. - self.suppressed_expected_diag = true; + assert!(matches!(diagnostic.level, Error | Warning | Allow)); self.future_breakage_diagnostics.push(diagnostic.clone()); } + // We call TRACK_DIAGNOSTIC with an empty closure for the cases that + // return early *and* have some kind of side-effect, except where + // noted. match diagnostic.level { Fatal | Error if self.treat_next_err_as_bug() => { // `Fatal` and `Error` can be promoted to `Bug`. @@ -1387,6 +1390,9 @@ impl DiagCtxtInner { return if let Some(guar) = self.has_errors() { Some(guar) } else { + // No `TRACK_DIAGNOSTIC` call is needed, because the + // incremental session is deleted if there is a delayed + // bug. This also saves us from cloning the diagnostic. let backtrace = std::backtrace::Backtrace::capture(); // This `unchecked_error_guaranteed` is valid. It is where the // `ErrorGuaranteed` for delayed bugs originates. See @@ -1401,11 +1407,17 @@ impl DiagCtxtInner { } Warning if !self.flags.can_emit_warnings => { if diagnostic.has_future_breakage() { + // The side-effect is at the top of this method. TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); } return None; } Allow => { + if diagnostic.has_future_breakage() { + // The side-effect is at the top of this method. + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + self.suppressed_expected_diag = true; + } return None; } Expect(expect_id) | ForceWarning(Some(expect_id)) => { @@ -1414,6 +1426,9 @@ impl DiagCtxtInner { // buffered until the `LintExpectationId` is replaced by a // stable one by the `LintLevelsBuilder`. if let LintExpectationId::Unstable { .. } = expect_id { + // We don't call TRACK_DIAGNOSTIC because we wait for the + // unstable ID to be updated, whereupon the diagnostic will + // be passed into this method again. self.unstable_expect_diagnostics.push(diagnostic); return None; } From 7ef605be3fa24f93194a3faf4f75d3a05ceca78a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 1 Mar 2024 13:46:00 +1100 Subject: [PATCH 7/7] Make the `match` in `emit_diagnostic` complete. This match is complex enough that it's a good idea to enumerate every variant. This also means `can_be_top_or_sub` can just be `can_be_subdiag`. --- compiler/rustc_errors/src/emitter.rs | 2 +- compiler/rustc_errors/src/lib.rs | 41 ++++++++++++++++------------ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 5637c05d04c69..212bda5ff0320 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -2104,7 +2104,7 @@ impl HumanEmitter { } if !self.short_message { for child in children { - assert!(child.level.can_be_top_or_sub().1); + assert!(child.level.can_be_subdiag()); let span = &child.span; if let Err(err) = self.emit_messages_default_inner( span, diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 738016f9b999c..069ac863d9939 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1354,8 +1354,6 @@ impl DiagCtxtInner { // Return value is only `Some` if the level is `Error` or `DelayedBug`. fn emit_diagnostic(&mut self, mut diagnostic: DiagInner) -> Option { - assert!(diagnostic.level.can_be_top_or_sub().0); - if diagnostic.has_future_breakage() { // Future breakages aren't emitted if they're Level::Allow, // but they still need to be constructed and stashed below, @@ -1368,9 +1366,12 @@ impl DiagCtxtInner { // return early *and* have some kind of side-effect, except where // noted. match diagnostic.level { - Fatal | Error if self.treat_next_err_as_bug() => { - // `Fatal` and `Error` can be promoted to `Bug`. - diagnostic.level = Bug; + Bug => {} + Fatal | Error => { + if self.treat_next_err_as_bug() { + // `Fatal` and `Error` can be promoted to `Bug`. + diagnostic.level = Bug; + } } DelayedBug => { // Note that because we check these conditions first, @@ -1405,14 +1406,21 @@ impl DiagCtxtInner { }; } } - Warning if !self.flags.can_emit_warnings => { - if diagnostic.has_future_breakage() { - // The side-effect is at the top of this method. - TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + ForceWarning(None) => {} // `ForceWarning(Some(...))` is below, with `Expect` + Warning => { + if !self.flags.can_emit_warnings { + // We are not emitting warnings. + if diagnostic.has_future_breakage() { + // The side-effect is at the top of this method. + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + } + return None; } - return None; } + Note | Help | FailureNote => {} + OnceNote | OnceHelp => panic!("bad level: {:?}", diagnostic.level), Allow => { + // Nothing emitted for allowed lints. if diagnostic.has_future_breakage() { // The side-effect is at the top of this method. TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); @@ -1434,12 +1442,12 @@ impl DiagCtxtInner { } self.fulfilled_expectations.insert(expect_id.normalize()); if let Expect(_) = diagnostic.level { + // Nothing emitted here for expected lints. TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); self.suppressed_expected_diag = true; return None; } } - _ => {} } TRACK_DIAGNOSTIC(diagnostic, &mut |mut diagnostic| { @@ -1816,16 +1824,13 @@ impl Level { matches!(*self, FailureNote) } - // Can this level be used in a top-level diagnostic message and/or a - // subdiagnostic message? - fn can_be_top_or_sub(&self) -> (bool, bool) { + // Can this level be used in a subdiagnostic message? + fn can_be_subdiag(&self) -> bool { match self { Bug | DelayedBug | Fatal | Error | ForceWarning(_) | FailureNote | Allow - | Expect(_) => (true, false), - - Warning | Note | Help => (true, true), + | Expect(_) => false, - OnceNote | OnceHelp => (false, true), + Warning | Note | Help | OnceNote | OnceHelp => true, } } }