about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-08-05 13:39:30 +0000
committerbors <bors@rust-lang.org>2024-08-05 13:39:30 +0000
commit22f7c0802f4c2c98c8acc1d441313908c5485168 (patch)
tree605c0fbfa64936ab17c32656959cf1158f4fe128
parent56a79223777c41d34d4d8d8135cd09e12e5e05c4 (diff)
parent80c8786408b0d1da972e1493300108f753bb922a (diff)
downloadrust-22f7c0802f4c2c98c8acc1d441313908c5485168.tar.gz
rust-22f7c0802f4c2c98c8acc1d441313908c5485168.zip
Auto merge of #17775 - ShoyuVanilla:segregate-diags, r=Veykril
perf: Segregate syntax and semantic diagnostics

Closes #17731
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs78
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs146
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs27
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs95
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs4
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs45
9 files changed, 255 insertions, 146 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
index 263ab747559..5db0c9a91ae 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
@@ -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>>> =
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
index e943503522f..46c6414caec 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
@@ -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,
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index 4624f6bfcf1..eff4bc3d376 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -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::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::diagnostics(db, config, &resolve, file_id))
+        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())
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
index cf5508bb7f1..06f4ba815d0 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -976,7 +976,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,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
index 5ec657a227a..cdac0e5ef5c 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -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(),
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs
index a7c9017cc17..75efdfd7dd8 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -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,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
index b23e7b7e98c..b99a8de2fc2 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs
@@ -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)
+            }
+            DiagnosticsTaskKind::Semantic(generation, diagnostics) => {
+                (generation, diagnostics, &mut self.native_semantic)
             }
-            if *old_gen < generation || generation == 0 {
-                self.native.insert(file_id, (generation, diagnostics));
+        };
+
+        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 {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
index f6543a82e57..28f4b809d6c 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -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();
     }
 }
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
index f8989b8479b..8c3472b7ee9 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
@@ -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,
@@ -87,11 +87,17 @@ pub(crate) enum QueuedTask {
 }
 
 #[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),