perf: Segregate syntax and semantic diagnostics

This commit is contained in:
Shoyu Vanilla 2024-08-03 03:14:03 +09:00
parent aa00ddcf65
commit eea1e9b21f
9 changed files with 255 additions and 146 deletions

View file

@ -96,6 +96,7 @@ use syntax::{
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum DiagnosticCode {
RustcHardError(&'static str),
SyntaxError,
RustcLint(&'static str),
Clippy(&'static str),
Ra(&'static str, Severity),
@ -107,6 +108,9 @@ impl DiagnosticCode {
DiagnosticCode::RustcHardError(e) => {
format!("https://doc.rust-lang.org/stable/error_codes/{e}.html")
}
DiagnosticCode::SyntaxError => {
String::from("https://doc.rust-lang.org/stable/reference/")
}
DiagnosticCode::RustcLint(e) => {
format!("https://doc.rust-lang.org/rustc/?search={e}")
}
@ -125,6 +129,7 @@ impl DiagnosticCode {
| DiagnosticCode::RustcLint(r)
| DiagnosticCode::Clippy(r)
| DiagnosticCode::Ra(r, _) => r,
DiagnosticCode::SyntaxError => "syntax-error",
}
}
}
@ -154,7 +159,7 @@ impl Diagnostic {
message,
range: range.into(),
severity: match code {
DiagnosticCode::RustcHardError(_) => Severity::Error,
DiagnosticCode::RustcHardError(_) | DiagnosticCode::SyntaxError => Severity::Error,
// FIXME: Rustc lints are not always warning, but the ones that are currently implemented are all warnings.
DiagnosticCode::RustcLint(_) => Severity::Warning,
// FIXME: We can make this configurable, and if the user uses `cargo clippy` on flycheck, we can
@ -297,31 +302,54 @@ impl DiagnosticsContext<'_> {
}
}
/// Request diagnostics for the given [`FileId`]. The produced diagnostics may point to other files
/// Request parser level diagnostics for the given [`FileId`].
pub fn syntax_diagnostics(
db: &RootDatabase,
config: &DiagnosticsConfig,
file_id: FileId,
) -> Vec<Diagnostic> {
let _p = tracing::info_span!("syntax_diagnostics").entered();
if config.disabled.contains("syntax-error") {
return Vec::new();
}
let sema = Semantics::new(db);
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
// [#3434] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
db.parse_errors(file_id)
.as_deref()
.into_iter()
.flatten()
.take(128)
.map(|err| {
Diagnostic::new(
DiagnosticCode::SyntaxError,
format!("Syntax Error: {err}"),
FileRange { file_id: file_id.into(), range: err.range() },
)
})
.collect()
}
/// Request semantic diagnostics for the given [`FileId`]. The produced diagnostics may point to other files
/// due to macros.
pub fn diagnostics(
pub fn semantic_diagnostics(
db: &RootDatabase,
config: &DiagnosticsConfig,
resolve: &AssistResolveStrategy,
file_id: FileId,
) -> Vec<Diagnostic> {
let _p = tracing::info_span!("diagnostics").entered();
let _p = tracing::info_span!("semantic_diagnostics").entered();
let sema = Semantics::new(db);
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
let mut res = Vec::new();
// [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
res.extend(db.parse_errors(file_id).as_deref().into_iter().flatten().take(128).map(|err| {
Diagnostic::new(
DiagnosticCode::RustcHardError("syntax-error"),
format!("Syntax Error: {err}"),
FileRange { file_id: file_id.into(), range: err.range() },
)
}));
let parse_errors = res.len();
let parse = sema.parse(file_id);
// FIXME: This iterates the entire file which is a rather expensive operation.
@ -341,8 +369,11 @@ pub fn diagnostics(
match module {
// A bunch of parse errors in a file indicate some bigger structural parse changes in the
// file, so we skip semantic diagnostics so we can show these faster.
Some(m) if parse_errors < 16 => m.diagnostics(db, &mut diags, config.style_lints),
Some(_) => (),
Some(m) => {
if !db.parse_errors(file_id).as_deref().is_some_and(|es| es.len() >= 16) {
m.diagnostics(db, &mut diags, config.style_lints);
}
}
None => handlers::unlinked_file::unlinked_file(&ctx, &mut res, file_id.file_id()),
}
@ -363,7 +394,7 @@ pub fn diagnostics(
res.extend(d.errors.iter().take(16).map(|err| {
{
Diagnostic::new(
DiagnosticCode::RustcHardError("syntax-error"),
DiagnosticCode::SyntaxError,
format!("Syntax Error in Expansion: {err}"),
ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
)
@ -464,6 +495,19 @@ pub fn diagnostics(
res
}
/// Request both syntax and semantic diagnostics for the given [`FileId`].
pub fn full_diagnostics(
db: &RootDatabase,
config: &DiagnosticsConfig,
resolve: &AssistResolveStrategy,
file_id: FileId,
) -> Vec<Diagnostic> {
let mut res = syntax_diagnostics(db, config, file_id);
let sema = semantic_diagnostics(db, config, resolve, file_id);
res.extend(sema);
res
}
// `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros
static RUSTC_LINT_GROUPS_DICT: Lazy<FxHashMap<&str, Vec<&str>>> =

View file

@ -59,10 +59,14 @@ fn check_nth_fix_with_config(
let after = trim_indent(ra_fixture_after);
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
let diagnostic =
super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_position.file_id.into())
.pop()
.expect("no diagnostics");
let diagnostic = super::full_diagnostics(
&db,
&config,
&AssistResolveStrategy::All,
file_position.file_id.into(),
)
.pop()
.expect("no diagnostics");
let fix = &diagnostic
.fixes
.unwrap_or_else(|| panic!("{:?} diagnostic misses fixes", diagnostic.code))[nth];
@ -102,37 +106,39 @@ pub(crate) fn check_has_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
let mut conf = DiagnosticsConfig::test_sample();
conf.expr_fill_default = ExprFillDefaultMode::Default;
let fix =
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id.into())
.into_iter()
.find(|d| {
d.fixes
.as_ref()
.and_then(|fixes| {
fixes.iter().find(|fix| {
if !fix.target.contains_inclusive(file_position.offset) {
return false;
}
let actual = {
let source_change = fix.source_change.as_ref().unwrap();
let file_id =
*source_change.source_file_edits.keys().next().unwrap();
let mut actual = db.file_text(file_id).to_string();
let fix = super::full_diagnostics(
&db,
&conf,
&AssistResolveStrategy::All,
file_position.file_id.into(),
)
.into_iter()
.find(|d| {
d.fixes
.as_ref()
.and_then(|fixes| {
fixes.iter().find(|fix| {
if !fix.target.contains_inclusive(file_position.offset) {
return false;
}
let actual = {
let source_change = fix.source_change.as_ref().unwrap();
let file_id = *source_change.source_file_edits.keys().next().unwrap();
let mut actual = db.file_text(file_id).to_string();
for (edit, snippet_edit) in source_change.source_file_edits.values()
{
edit.apply(&mut actual);
if let Some(snippet_edit) = snippet_edit {
snippet_edit.apply(&mut actual);
}
}
actual
};
after == actual
})
})
.is_some()
});
for (edit, snippet_edit) in source_change.source_file_edits.values() {
edit.apply(&mut actual);
if let Some(snippet_edit) = snippet_edit {
snippet_edit.apply(&mut actual);
}
}
actual
};
after == actual
})
})
.is_some()
});
assert!(fix.is_some(), "no diagnostic with desired fix");
}
@ -144,38 +150,40 @@ pub(crate) fn check_has_single_fix(ra_fixture_before: &str, ra_fixture_after: &s
let mut conf = DiagnosticsConfig::test_sample();
conf.expr_fill_default = ExprFillDefaultMode::Default;
let mut n_fixes = 0;
let fix =
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id.into())
.into_iter()
.find(|d| {
d.fixes
.as_ref()
.and_then(|fixes| {
n_fixes += fixes.len();
fixes.iter().find(|fix| {
if !fix.target.contains_inclusive(file_position.offset) {
return false;
}
let actual = {
let source_change = fix.source_change.as_ref().unwrap();
let file_id =
*source_change.source_file_edits.keys().next().unwrap();
let mut actual = db.file_text(file_id).to_string();
let fix = super::full_diagnostics(
&db,
&conf,
&AssistResolveStrategy::All,
file_position.file_id.into(),
)
.into_iter()
.find(|d| {
d.fixes
.as_ref()
.and_then(|fixes| {
n_fixes += fixes.len();
fixes.iter().find(|fix| {
if !fix.target.contains_inclusive(file_position.offset) {
return false;
}
let actual = {
let source_change = fix.source_change.as_ref().unwrap();
let file_id = *source_change.source_file_edits.keys().next().unwrap();
let mut actual = db.file_text(file_id).to_string();
for (edit, snippet_edit) in source_change.source_file_edits.values()
{
edit.apply(&mut actual);
if let Some(snippet_edit) = snippet_edit {
snippet_edit.apply(&mut actual);
}
}
actual
};
after == actual
})
})
.is_some()
});
for (edit, snippet_edit) in source_change.source_file_edits.values() {
edit.apply(&mut actual);
if let Some(snippet_edit) = snippet_edit {
snippet_edit.apply(&mut actual);
}
}
actual
};
after == actual
})
})
.is_some()
});
assert!(fix.is_some(), "no diagnostic with desired fix");
assert!(n_fixes == 1, "Too many fixes suggested");
}
@ -183,7 +191,7 @@ pub(crate) fn check_has_single_fix(ra_fixture_before: &str, ra_fixture_after: &s
/// Checks that there's a diagnostic *without* fix at `$0`.
pub(crate) fn check_no_fix(ra_fixture: &str) {
let (db, file_position) = RootDatabase::with_position(ra_fixture);
let diagnostic = super::diagnostics(
let diagnostic = super::full_diagnostics(
&db,
&DiagnosticsConfig::test_sample(),
&AssistResolveStrategy::All,
@ -215,7 +223,7 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
.iter()
.copied()
.flat_map(|file_id| {
super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id.into())
super::full_diagnostics(&db, &config, &AssistResolveStrategy::All, file_id.into())
.into_iter()
.map(|d| {
let mut annotation = String::new();
@ -277,10 +285,10 @@ fn test_disabled_diagnostics() {
let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
let file_id = file_id.into();
let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
let diagnostics = super::full_diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
assert!(diagnostics.is_empty());
let diagnostics = super::diagnostics(
let diagnostics = super::full_diagnostics(
&db,
&DiagnosticsConfig::test_sample(),
&AssistResolveStrategy::All,

View file

@ -672,14 +672,33 @@ impl Analysis {
.unwrap_or_default())
}
/// Computes the set of diagnostics for the given file.
pub fn diagnostics(
/// Computes the set of parser level diagnostics for the given file.
pub fn syntax_diagnostics(
&self,
config: &DiagnosticsConfig,
file_id: FileId,
) -> Cancellable<Vec<Diagnostic>> {
self.with_db(|db| ide_diagnostics::syntax_diagnostics(db, config, file_id))
}
/// Computes the set of semantic diagnostics for the given file.
pub fn semantic_diagnostics(
&self,
config: &DiagnosticsConfig,
resolve: AssistResolveStrategy,
file_id: FileId,
) -> Cancellable<Vec<Diagnostic>> {
self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id))
self.with_db(|db| ide_diagnostics::semantic_diagnostics(db, config, &resolve, file_id))
}
/// Computes the set of both syntax and semantic diagnostics for the given file.
pub fn full_diagnostics(
&self,
config: &DiagnosticsConfig,
resolve: AssistResolveStrategy,
file_id: FileId,
) -> Cancellable<Vec<Diagnostic>> {
self.with_db(|db| ide_diagnostics::full_diagnostics(db, config, &resolve, file_id))
}
/// Convenience function to return assists + quick fixes for diagnostics
@ -697,7 +716,7 @@ impl Analysis {
self.with_db(|db| {
let diagnostic_assists = if diagnostics_config.enabled && include_fixes {
ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
ide_diagnostics::full_diagnostics(db, diagnostics_config, &resolve, frange.file_id)
.into_iter()
.flat_map(|it| it.fixes.unwrap_or_default())
.filter(|it| it.target.intersect(frange.range).is_some())

View file

@ -977,7 +977,7 @@ impl flags::AnalysisStats {
let mut sw = self.stop_watch();
for &file_id in &file_ids {
_ = analysis.diagnostics(
_ = analysis.full_diagnostics(
&DiagnosticsConfig {
enabled: true,
proc_macros_enabled: true,

View file

@ -63,7 +63,7 @@ impl flags::Diagnostics {
_vfs.file_path(file_id.into())
);
for diagnostic in analysis
.diagnostics(
.full_diagnostics(
&DiagnosticsConfig::test_sample(),
AssistResolveStrategy::None,
file_id.into(),

View file

@ -155,7 +155,7 @@ impl Tester {
let root_file = self.root_file;
move || {
let res = std::panic::catch_unwind(move || {
analysis.diagnostics(
analysis.full_diagnostics(
diagnostic_config,
ide::AssistResolveStrategy::None,
root_file,

View file

@ -11,7 +11,7 @@ use rustc_hash::FxHashSet;
use stdx::iter_eq_by;
use triomphe::Arc;
use crate::{global_state::GlobalStateSnapshot, lsp, lsp_ext};
use crate::{global_state::GlobalStateSnapshot, lsp, lsp_ext, main_loop::DiagnosticsTaskKind};
pub(crate) type CheckFixes = Arc<IntMap<usize, IntMap<FileId, Vec<Fix>>>>;
@ -28,7 +28,8 @@ pub(crate) type DiagnosticsGeneration = usize;
#[derive(Debug, Default, Clone)]
pub(crate) struct DiagnosticCollection {
// FIXME: should be IntMap<FileId, Vec<ra_id::Diagnostic>>
pub(crate) native: IntMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
pub(crate) native_syntax: IntMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
pub(crate) native_semantic: IntMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
// FIXME: should be Vec<flycheck::Diagnostic>
pub(crate) check: IntMap<usize, IntMap<FileId, Vec<lsp_types::Diagnostic>>>,
pub(crate) check_fixes: CheckFixes,
@ -64,7 +65,8 @@ impl DiagnosticCollection {
}
pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
self.native.remove(&file_id);
self.native_syntax.remove(&file_id);
self.native_semantic.remove(&file_id);
self.changes.insert(file_id);
}
@ -88,43 +90,51 @@ impl DiagnosticCollection {
self.changes.insert(file_id);
}
pub(crate) fn set_native_diagnostics(
&mut self,
generation: DiagnosticsGeneration,
file_id: FileId,
mut diagnostics: Vec<lsp_types::Diagnostic>,
) {
diagnostics.sort_by_key(|it| (it.range.start, it.range.end));
if let Some((old_gen, existing_diagnostics)) = self.native.get_mut(&file_id) {
if existing_diagnostics.len() == diagnostics.len()
&& iter_eq_by(&diagnostics, &*existing_diagnostics, |new, existing| {
are_diagnostics_equal(new, existing)
})
{
// don't signal an update if the diagnostics are the same
return;
pub(crate) fn set_native_diagnostics(&mut self, kind: DiagnosticsTaskKind) {
let (generation, diagnostics, target) = match kind {
DiagnosticsTaskKind::Syntax(generation, diagnostics) => {
(generation, diagnostics, &mut self.native_syntax)
}
if *old_gen < generation || generation == 0 {
self.native.insert(file_id, (generation, diagnostics));
DiagnosticsTaskKind::Semantic(generation, diagnostics) => {
(generation, diagnostics, &mut self.native_semantic)
}
};
for (file_id, mut diagnostics) in diagnostics {
diagnostics.sort_by_key(|it| (it.range.start, it.range.end));
if let Some((old_gen, existing_diagnostics)) = target.get_mut(&file_id) {
if existing_diagnostics.len() == diagnostics.len()
&& iter_eq_by(&diagnostics, &*existing_diagnostics, |new, existing| {
are_diagnostics_equal(new, existing)
})
{
// don't signal an update if the diagnostics are the same
return;
}
if *old_gen < generation || generation == 0 {
target.insert(file_id, (generation, diagnostics));
} else {
existing_diagnostics.extend(diagnostics);
// FIXME: Doing the merge step of a merge sort here would be a bit more performant
// but eh
existing_diagnostics.sort_by_key(|it| (it.range.start, it.range.end))
}
} else {
existing_diagnostics.extend(diagnostics);
// FIXME: Doing the merge step of a merge sort here would be a bit more performant
// but eh
existing_diagnostics.sort_by_key(|it| (it.range.start, it.range.end))
target.insert(file_id, (generation, diagnostics));
}
} else {
self.native.insert(file_id, (generation, diagnostics));
self.changes.insert(file_id);
}
self.changes.insert(file_id);
}
pub(crate) fn diagnostics_for(
&self,
file_id: FileId,
) -> impl Iterator<Item = &lsp_types::Diagnostic> {
let native = self.native.get(&file_id).into_iter().flat_map(|(_, d)| d);
let native_syntax = self.native_syntax.get(&file_id).into_iter().flat_map(|(_, d)| d);
let native_semantic = self.native_semantic.get(&file_id).into_iter().flat_map(|(_, d)| d);
let check = self.check.values().filter_map(move |it| it.get(&file_id)).flatten();
native.chain(check)
native_syntax.chain(native_semantic).chain(check)
}
pub(crate) fn take_changes(&mut self) -> Option<IntSet<FileId>> {
@ -147,10 +157,16 @@ fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagno
&& left.message == right.message
}
pub(crate) enum NativeDiagnosticsFetchKind {
Syntax,
Semantic,
}
pub(crate) fn fetch_native_diagnostics(
snapshot: GlobalStateSnapshot,
snapshot: &GlobalStateSnapshot,
subscriptions: std::sync::Arc<[FileId]>,
slice: std::ops::Range<usize>,
kind: NativeDiagnosticsFetchKind,
) -> Vec<(FileId, Vec<lsp_types::Diagnostic>)> {
let _p = tracing::info_span!("fetch_native_diagnostics").entered();
let _ctx = stdx::panic_context::enter("fetch_native_diagnostics".to_owned());
@ -180,14 +196,17 @@ pub(crate) fn fetch_native_diagnostics(
let line_index = snapshot.file_line_index(file_id).ok()?;
let source_root = snapshot.analysis.source_root_id(file_id).ok()?;
let diagnostics = snapshot
.analysis
.diagnostics(
&snapshot.config.diagnostics(Some(source_root)),
ide::AssistResolveStrategy::None,
file_id,
)
.ok()?
let config = &snapshot.config.diagnostics(Some(source_root));
let diagnostics = match kind {
NativeDiagnosticsFetchKind::Syntax => {
snapshot.analysis.syntax_diagnostics(config, file_id).ok()?
}
NativeDiagnosticsFetchKind::Semantic => snapshot
.analysis
.semantic_diagnostics(config, ide::AssistResolveStrategy::None, file_id)
.ok()?,
};
let diagnostics = diagnostics
.into_iter()
.filter_map(|d| {
if d.range.file_id == file_id {

View file

@ -325,7 +325,7 @@ fn integrated_diagnostics_benchmark() {
term_search_borrowck: true,
};
host.analysis()
.diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
.full_diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
.unwrap();
let _g = crate::tracing::hprof::init("*");
@ -343,7 +343,7 @@ fn integrated_diagnostics_benchmark() {
let _p = tracing::info_span!("diagnostics").entered();
let _span = profile::cpu_span();
host.analysis()
.diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
.full_diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
.unwrap();
}
}

View file

@ -19,7 +19,7 @@ use vfs::{AbsPathBuf, FileId};
use crate::{
config::Config,
diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration},
diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration, NativeDiagnosticsFetchKind},
dispatch::{NotificationDispatcher, RequestDispatcher},
global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState},
hack_recover_crate_name,
@ -86,12 +86,18 @@ pub(crate) enum QueuedTask {
CheckProcMacroSources(Vec<FileId>),
}
#[derive(Debug)]
pub(crate) enum DiagnosticsTaskKind {
Syntax(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
Semantic(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
}
#[derive(Debug)]
pub(crate) enum Task {
Response(lsp_server::Response),
DiscoverLinkedProjects(DiscoverProjectParam),
Retry(lsp_server::Request),
Diagnostics(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
Diagnostics(DiagnosticsTaskKind),
DiscoverTest(lsp_ext::DiscoverTestResults),
PrimeCaches(PrimeCachesProgress),
FetchWorkspace(ProjectWorkspaceProgress),
@ -549,14 +555,29 @@ impl GlobalState {
}
// Diagnostics are triggered by the user typing
// so we run them on a latency sensitive thread.
self.task_pool.handle.spawn(ThreadIntent::LatencySensitive, {
let snapshot = self.snapshot();
let snapshot = self.snapshot();
self.task_pool.handle.spawn_with_sender(ThreadIntent::LatencySensitive, {
let subscriptions = subscriptions.clone();
move || {
Task::Diagnostics(
generation,
fetch_native_diagnostics(snapshot, subscriptions, slice),
)
move |sender| {
let diags = fetch_native_diagnostics(
&snapshot,
subscriptions.clone(),
slice.clone(),
NativeDiagnosticsFetchKind::Syntax,
);
sender
.send(Task::Diagnostics(DiagnosticsTaskKind::Syntax(generation, diags)))
.unwrap();
let diags = fetch_native_diagnostics(
&snapshot,
subscriptions,
slice,
NativeDiagnosticsFetchKind::Semantic,
);
sender
.send(Task::Diagnostics(DiagnosticsTaskKind::Semantic(generation, diags)))
.unwrap();
}
});
start = end;
@ -644,10 +665,8 @@ impl GlobalState {
// Only retry requests that haven't been cancelled. Otherwise we do unnecessary work.
Task::Retry(req) if !self.is_completed(&req) => self.on_request(req),
Task::Retry(_) => (),
Task::Diagnostics(generation, diagnostics_per_file) => {
for (file_id, diagnostics) in diagnostics_per_file {
self.diagnostics.set_native_diagnostics(generation, file_id, diagnostics)
}
Task::Diagnostics(kind) => {
self.diagnostics.set_native_diagnostics(kind);
}
Task::PrimeCaches(progress) => match progress {
PrimeCachesProgress::Begin => prime_caches_progress.push(progress),