mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 21:05:02 +00:00
Auto merge of #17775 - ShoyuVanilla:segregate-diags, r=Veykril
perf: Segregate syntax and semantic diagnostics Closes #17731
This commit is contained in:
commit
f62d7b9f11
9 changed files with 255 additions and 146 deletions
|
@ -96,6 +96,7 @@ use syntax::{
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum DiagnosticCode {
|
pub enum DiagnosticCode {
|
||||||
RustcHardError(&'static str),
|
RustcHardError(&'static str),
|
||||||
|
SyntaxError,
|
||||||
RustcLint(&'static str),
|
RustcLint(&'static str),
|
||||||
Clippy(&'static str),
|
Clippy(&'static str),
|
||||||
Ra(&'static str, Severity),
|
Ra(&'static str, Severity),
|
||||||
|
@ -107,6 +108,9 @@ impl DiagnosticCode {
|
||||||
DiagnosticCode::RustcHardError(e) => {
|
DiagnosticCode::RustcHardError(e) => {
|
||||||
format!("https://doc.rust-lang.org/stable/error_codes/{e}.html")
|
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) => {
|
DiagnosticCode::RustcLint(e) => {
|
||||||
format!("https://doc.rust-lang.org/rustc/?search={e}")
|
format!("https://doc.rust-lang.org/rustc/?search={e}")
|
||||||
}
|
}
|
||||||
|
@ -125,6 +129,7 @@ impl DiagnosticCode {
|
||||||
| DiagnosticCode::RustcLint(r)
|
| DiagnosticCode::RustcLint(r)
|
||||||
| DiagnosticCode::Clippy(r)
|
| DiagnosticCode::Clippy(r)
|
||||||
| DiagnosticCode::Ra(r, _) => r,
|
| DiagnosticCode::Ra(r, _) => r,
|
||||||
|
DiagnosticCode::SyntaxError => "syntax-error",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +159,7 @@ impl Diagnostic {
|
||||||
message,
|
message,
|
||||||
range: range.into(),
|
range: range.into(),
|
||||||
severity: match code {
|
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.
|
// FIXME: Rustc lints are not always warning, but the ones that are currently implemented are all warnings.
|
||||||
DiagnosticCode::RustcLint(_) => Severity::Warning,
|
DiagnosticCode::RustcLint(_) => Severity::Warning,
|
||||||
// FIXME: We can make this configurable, and if the user uses `cargo clippy` on flycheck, we can
|
// 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.
|
/// due to macros.
|
||||||
pub fn diagnostics(
|
pub fn semantic_diagnostics(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
config: &DiagnosticsConfig,
|
config: &DiagnosticsConfig,
|
||||||
resolve: &AssistResolveStrategy,
|
resolve: &AssistResolveStrategy,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
let _p = tracing::info_span!("diagnostics").entered();
|
let _p = tracing::info_span!("semantic_diagnostics").entered();
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let file_id = sema
|
let file_id = sema
|
||||||
.attach_first_edition(file_id)
|
.attach_first_edition(file_id)
|
||||||
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
|
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
|
||||||
let mut res = Vec::new();
|
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);
|
let parse = sema.parse(file_id);
|
||||||
|
|
||||||
// FIXME: This iterates the entire file which is a rather expensive operation.
|
// FIXME: This iterates the entire file which is a rather expensive operation.
|
||||||
|
@ -341,8 +369,11 @@ pub fn diagnostics(
|
||||||
match module {
|
match module {
|
||||||
// A bunch of parse errors in a file indicate some bigger structural parse changes in the
|
// 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.
|
// 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(m) => {
|
||||||
Some(_) => (),
|
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()),
|
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| {
|
res.extend(d.errors.iter().take(16).map(|err| {
|
||||||
{
|
{
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
DiagnosticCode::RustcHardError("syntax-error"),
|
DiagnosticCode::SyntaxError,
|
||||||
format!("Syntax Error in Expansion: {err}"),
|
format!("Syntax Error in Expansion: {err}"),
|
||||||
ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
|
ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
|
||||||
)
|
)
|
||||||
|
@ -464,6 +495,19 @@ pub fn diagnostics(
|
||||||
res
|
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
|
// `__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>>> =
|
static RUSTC_LINT_GROUPS_DICT: Lazy<FxHashMap<&str, Vec<&str>>> =
|
||||||
|
|
|
@ -59,10 +59,14 @@ fn check_nth_fix_with_config(
|
||||||
let after = trim_indent(ra_fixture_after);
|
let after = trim_indent(ra_fixture_after);
|
||||||
|
|
||||||
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
|
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
|
||||||
let diagnostic =
|
let diagnostic = super::full_diagnostics(
|
||||||
super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_position.file_id.into())
|
&db,
|
||||||
.pop()
|
&config,
|
||||||
.expect("no diagnostics");
|
&AssistResolveStrategy::All,
|
||||||
|
file_position.file_id.into(),
|
||||||
|
)
|
||||||
|
.pop()
|
||||||
|
.expect("no diagnostics");
|
||||||
let fix = &diagnostic
|
let fix = &diagnostic
|
||||||
.fixes
|
.fixes
|
||||||
.unwrap_or_else(|| panic!("{:?} diagnostic misses fixes", diagnostic.code))[nth];
|
.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 (db, file_position) = RootDatabase::with_position(ra_fixture_before);
|
||||||
let mut conf = DiagnosticsConfig::test_sample();
|
let mut conf = DiagnosticsConfig::test_sample();
|
||||||
conf.expr_fill_default = ExprFillDefaultMode::Default;
|
conf.expr_fill_default = ExprFillDefaultMode::Default;
|
||||||
let fix =
|
let fix = super::full_diagnostics(
|
||||||
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id.into())
|
&db,
|
||||||
.into_iter()
|
&conf,
|
||||||
.find(|d| {
|
&AssistResolveStrategy::All,
|
||||||
d.fixes
|
file_position.file_id.into(),
|
||||||
.as_ref()
|
)
|
||||||
.and_then(|fixes| {
|
.into_iter()
|
||||||
fixes.iter().find(|fix| {
|
.find(|d| {
|
||||||
if !fix.target.contains_inclusive(file_position.offset) {
|
d.fixes
|
||||||
return false;
|
.as_ref()
|
||||||
}
|
.and_then(|fixes| {
|
||||||
let actual = {
|
fixes.iter().find(|fix| {
|
||||||
let source_change = fix.source_change.as_ref().unwrap();
|
if !fix.target.contains_inclusive(file_position.offset) {
|
||||||
let file_id =
|
return false;
|
||||||
*source_change.source_file_edits.keys().next().unwrap();
|
}
|
||||||
let mut actual = db.file_text(file_id).to_string();
|
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()
|
for (edit, snippet_edit) in source_change.source_file_edits.values() {
|
||||||
{
|
edit.apply(&mut actual);
|
||||||
edit.apply(&mut actual);
|
if let Some(snippet_edit) = snippet_edit {
|
||||||
if let Some(snippet_edit) = snippet_edit {
|
snippet_edit.apply(&mut actual);
|
||||||
snippet_edit.apply(&mut actual);
|
}
|
||||||
}
|
}
|
||||||
}
|
actual
|
||||||
actual
|
};
|
||||||
};
|
after == actual
|
||||||
after == actual
|
})
|
||||||
})
|
})
|
||||||
})
|
.is_some()
|
||||||
.is_some()
|
});
|
||||||
});
|
|
||||||
assert!(fix.is_some(), "no diagnostic with desired fix");
|
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();
|
let mut conf = DiagnosticsConfig::test_sample();
|
||||||
conf.expr_fill_default = ExprFillDefaultMode::Default;
|
conf.expr_fill_default = ExprFillDefaultMode::Default;
|
||||||
let mut n_fixes = 0;
|
let mut n_fixes = 0;
|
||||||
let fix =
|
let fix = super::full_diagnostics(
|
||||||
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id.into())
|
&db,
|
||||||
.into_iter()
|
&conf,
|
||||||
.find(|d| {
|
&AssistResolveStrategy::All,
|
||||||
d.fixes
|
file_position.file_id.into(),
|
||||||
.as_ref()
|
)
|
||||||
.and_then(|fixes| {
|
.into_iter()
|
||||||
n_fixes += fixes.len();
|
.find(|d| {
|
||||||
fixes.iter().find(|fix| {
|
d.fixes
|
||||||
if !fix.target.contains_inclusive(file_position.offset) {
|
.as_ref()
|
||||||
return false;
|
.and_then(|fixes| {
|
||||||
}
|
n_fixes += fixes.len();
|
||||||
let actual = {
|
fixes.iter().find(|fix| {
|
||||||
let source_change = fix.source_change.as_ref().unwrap();
|
if !fix.target.contains_inclusive(file_position.offset) {
|
||||||
let file_id =
|
return false;
|
||||||
*source_change.source_file_edits.keys().next().unwrap();
|
}
|
||||||
let mut actual = db.file_text(file_id).to_string();
|
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()
|
for (edit, snippet_edit) in source_change.source_file_edits.values() {
|
||||||
{
|
edit.apply(&mut actual);
|
||||||
edit.apply(&mut actual);
|
if let Some(snippet_edit) = snippet_edit {
|
||||||
if let Some(snippet_edit) = snippet_edit {
|
snippet_edit.apply(&mut actual);
|
||||||
snippet_edit.apply(&mut actual);
|
}
|
||||||
}
|
}
|
||||||
}
|
actual
|
||||||
actual
|
};
|
||||||
};
|
after == actual
|
||||||
after == actual
|
})
|
||||||
})
|
})
|
||||||
})
|
.is_some()
|
||||||
.is_some()
|
});
|
||||||
});
|
|
||||||
assert!(fix.is_some(), "no diagnostic with desired fix");
|
assert!(fix.is_some(), "no diagnostic with desired fix");
|
||||||
assert!(n_fixes == 1, "Too many fixes suggested");
|
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`.
|
/// Checks that there's a diagnostic *without* fix at `$0`.
|
||||||
pub(crate) fn check_no_fix(ra_fixture: &str) {
|
pub(crate) fn check_no_fix(ra_fixture: &str) {
|
||||||
let (db, file_position) = RootDatabase::with_position(ra_fixture);
|
let (db, file_position) = RootDatabase::with_position(ra_fixture);
|
||||||
let diagnostic = super::diagnostics(
|
let diagnostic = super::full_diagnostics(
|
||||||
&db,
|
&db,
|
||||||
&DiagnosticsConfig::test_sample(),
|
&DiagnosticsConfig::test_sample(),
|
||||||
&AssistResolveStrategy::All,
|
&AssistResolveStrategy::All,
|
||||||
|
@ -215,7 +223,7 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.flat_map(|file_id| {
|
.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()
|
.into_iter()
|
||||||
.map(|d| {
|
.map(|d| {
|
||||||
let mut annotation = String::new();
|
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 (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
|
||||||
let file_id = file_id.into();
|
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());
|
assert!(diagnostics.is_empty());
|
||||||
|
|
||||||
let diagnostics = super::diagnostics(
|
let diagnostics = super::full_diagnostics(
|
||||||
&db,
|
&db,
|
||||||
&DiagnosticsConfig::test_sample(),
|
&DiagnosticsConfig::test_sample(),
|
||||||
&AssistResolveStrategy::All,
|
&AssistResolveStrategy::All,
|
||||||
|
|
|
@ -672,14 +672,33 @@ impl Analysis {
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the set of diagnostics for the given file.
|
/// Computes the set of parser level diagnostics for the given file.
|
||||||
pub fn diagnostics(
|
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,
|
&self,
|
||||||
config: &DiagnosticsConfig,
|
config: &DiagnosticsConfig,
|
||||||
resolve: AssistResolveStrategy,
|
resolve: AssistResolveStrategy,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Cancellable<Vec<Diagnostic>> {
|
) -> 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
|
/// Convenience function to return assists + quick fixes for diagnostics
|
||||||
|
@ -697,7 +716,7 @@ impl Analysis {
|
||||||
|
|
||||||
self.with_db(|db| {
|
self.with_db(|db| {
|
||||||
let diagnostic_assists = if diagnostics_config.enabled && include_fixes {
|
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()
|
.into_iter()
|
||||||
.flat_map(|it| it.fixes.unwrap_or_default())
|
.flat_map(|it| it.fixes.unwrap_or_default())
|
||||||
.filter(|it| it.target.intersect(frange.range).is_some())
|
.filter(|it| it.target.intersect(frange.range).is_some())
|
||||||
|
|
|
@ -976,7 +976,7 @@ impl flags::AnalysisStats {
|
||||||
let mut sw = self.stop_watch();
|
let mut sw = self.stop_watch();
|
||||||
|
|
||||||
for &file_id in &file_ids {
|
for &file_id in &file_ids {
|
||||||
_ = analysis.diagnostics(
|
_ = analysis.full_diagnostics(
|
||||||
&DiagnosticsConfig {
|
&DiagnosticsConfig {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
proc_macros_enabled: true,
|
proc_macros_enabled: true,
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl flags::Diagnostics {
|
||||||
_vfs.file_path(file_id.into())
|
_vfs.file_path(file_id.into())
|
||||||
);
|
);
|
||||||
for diagnostic in analysis
|
for diagnostic in analysis
|
||||||
.diagnostics(
|
.full_diagnostics(
|
||||||
&DiagnosticsConfig::test_sample(),
|
&DiagnosticsConfig::test_sample(),
|
||||||
AssistResolveStrategy::None,
|
AssistResolveStrategy::None,
|
||||||
file_id.into(),
|
file_id.into(),
|
||||||
|
|
|
@ -155,7 +155,7 @@ impl Tester {
|
||||||
let root_file = self.root_file;
|
let root_file = self.root_file;
|
||||||
move || {
|
move || {
|
||||||
let res = std::panic::catch_unwind(move || {
|
let res = std::panic::catch_unwind(move || {
|
||||||
analysis.diagnostics(
|
analysis.full_diagnostics(
|
||||||
diagnostic_config,
|
diagnostic_config,
|
||||||
ide::AssistResolveStrategy::None,
|
ide::AssistResolveStrategy::None,
|
||||||
root_file,
|
root_file,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use rustc_hash::FxHashSet;
|
||||||
use stdx::iter_eq_by;
|
use stdx::iter_eq_by;
|
||||||
use triomphe::Arc;
|
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>>>>;
|
pub(crate) type CheckFixes = Arc<IntMap<usize, IntMap<FileId, Vec<Fix>>>>;
|
||||||
|
|
||||||
|
@ -28,7 +28,8 @@ pub(crate) type DiagnosticsGeneration = usize;
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub(crate) struct DiagnosticCollection {
|
pub(crate) struct DiagnosticCollection {
|
||||||
// FIXME: should be IntMap<FileId, Vec<ra_id::Diagnostic>>
|
// 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>
|
// FIXME: should be Vec<flycheck::Diagnostic>
|
||||||
pub(crate) check: IntMap<usize, IntMap<FileId, Vec<lsp_types::Diagnostic>>>,
|
pub(crate) check: IntMap<usize, IntMap<FileId, Vec<lsp_types::Diagnostic>>>,
|
||||||
pub(crate) check_fixes: CheckFixes,
|
pub(crate) check_fixes: CheckFixes,
|
||||||
|
@ -64,7 +65,8 @@ impl DiagnosticCollection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
|
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);
|
self.changes.insert(file_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,43 +90,51 @@ impl DiagnosticCollection {
|
||||||
self.changes.insert(file_id);
|
self.changes.insert(file_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_native_diagnostics(
|
pub(crate) fn set_native_diagnostics(&mut self, kind: DiagnosticsTaskKind) {
|
||||||
&mut self,
|
let (generation, diagnostics, target) = match kind {
|
||||||
generation: DiagnosticsGeneration,
|
DiagnosticsTaskKind::Syntax(generation, diagnostics) => {
|
||||||
file_id: FileId,
|
(generation, diagnostics, &mut self.native_syntax)
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
if *old_gen < generation || generation == 0 {
|
DiagnosticsTaskKind::Semantic(generation, diagnostics) => {
|
||||||
self.native.insert(file_id, (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 {
|
} else {
|
||||||
existing_diagnostics.extend(diagnostics);
|
target.insert(file_id, (generation, 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 {
|
self.changes.insert(file_id);
|
||||||
self.native.insert(file_id, (generation, diagnostics));
|
|
||||||
}
|
}
|
||||||
self.changes.insert(file_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn diagnostics_for(
|
pub(crate) fn diagnostics_for(
|
||||||
&self,
|
&self,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> impl Iterator<Item = &lsp_types::Diagnostic> {
|
) -> 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();
|
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>> {
|
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
|
&& left.message == right.message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum NativeDiagnosticsFetchKind {
|
||||||
|
Syntax,
|
||||||
|
Semantic,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn fetch_native_diagnostics(
|
pub(crate) fn fetch_native_diagnostics(
|
||||||
snapshot: GlobalStateSnapshot,
|
snapshot: &GlobalStateSnapshot,
|
||||||
subscriptions: std::sync::Arc<[FileId]>,
|
subscriptions: std::sync::Arc<[FileId]>,
|
||||||
slice: std::ops::Range<usize>,
|
slice: std::ops::Range<usize>,
|
||||||
|
kind: NativeDiagnosticsFetchKind,
|
||||||
) -> Vec<(FileId, Vec<lsp_types::Diagnostic>)> {
|
) -> Vec<(FileId, Vec<lsp_types::Diagnostic>)> {
|
||||||
let _p = tracing::info_span!("fetch_native_diagnostics").entered();
|
let _p = tracing::info_span!("fetch_native_diagnostics").entered();
|
||||||
let _ctx = stdx::panic_context::enter("fetch_native_diagnostics".to_owned());
|
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 line_index = snapshot.file_line_index(file_id).ok()?;
|
||||||
let source_root = snapshot.analysis.source_root_id(file_id).ok()?;
|
let source_root = snapshot.analysis.source_root_id(file_id).ok()?;
|
||||||
|
|
||||||
let diagnostics = snapshot
|
let config = &snapshot.config.diagnostics(Some(source_root));
|
||||||
.analysis
|
let diagnostics = match kind {
|
||||||
.diagnostics(
|
NativeDiagnosticsFetchKind::Syntax => {
|
||||||
&snapshot.config.diagnostics(Some(source_root)),
|
snapshot.analysis.syntax_diagnostics(config, file_id).ok()?
|
||||||
ide::AssistResolveStrategy::None,
|
}
|
||||||
file_id,
|
NativeDiagnosticsFetchKind::Semantic => snapshot
|
||||||
)
|
.analysis
|
||||||
.ok()?
|
.semantic_diagnostics(config, ide::AssistResolveStrategy::None, file_id)
|
||||||
|
.ok()?,
|
||||||
|
};
|
||||||
|
let diagnostics = diagnostics
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|d| {
|
.filter_map(|d| {
|
||||||
if d.range.file_id == file_id {
|
if d.range.file_id == file_id {
|
||||||
|
|
|
@ -325,7 +325,7 @@ fn integrated_diagnostics_benchmark() {
|
||||||
term_search_borrowck: true,
|
term_search_borrowck: true,
|
||||||
};
|
};
|
||||||
host.analysis()
|
host.analysis()
|
||||||
.diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
|
.full_diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let _g = crate::tracing::hprof::init("*");
|
let _g = crate::tracing::hprof::init("*");
|
||||||
|
@ -343,7 +343,7 @@ fn integrated_diagnostics_benchmark() {
|
||||||
let _p = tracing::info_span!("diagnostics").entered();
|
let _p = tracing::info_span!("diagnostics").entered();
|
||||||
let _span = profile::cpu_span();
|
let _span = profile::cpu_span();
|
||||||
host.analysis()
|
host.analysis()
|
||||||
.diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
|
.full_diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use vfs::{AbsPathBuf, FileId};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration},
|
diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration, NativeDiagnosticsFetchKind},
|
||||||
dispatch::{NotificationDispatcher, RequestDispatcher},
|
dispatch::{NotificationDispatcher, RequestDispatcher},
|
||||||
global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState},
|
global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState},
|
||||||
hack_recover_crate_name,
|
hack_recover_crate_name,
|
||||||
|
@ -86,12 +86,18 @@ pub(crate) enum QueuedTask {
|
||||||
CheckProcMacroSources(Vec<FileId>),
|
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)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum Task {
|
pub(crate) enum Task {
|
||||||
Response(lsp_server::Response),
|
Response(lsp_server::Response),
|
||||||
DiscoverLinkedProjects(DiscoverProjectParam),
|
DiscoverLinkedProjects(DiscoverProjectParam),
|
||||||
Retry(lsp_server::Request),
|
Retry(lsp_server::Request),
|
||||||
Diagnostics(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
|
Diagnostics(DiagnosticsTaskKind),
|
||||||
DiscoverTest(lsp_ext::DiscoverTestResults),
|
DiscoverTest(lsp_ext::DiscoverTestResults),
|
||||||
PrimeCaches(PrimeCachesProgress),
|
PrimeCaches(PrimeCachesProgress),
|
||||||
FetchWorkspace(ProjectWorkspaceProgress),
|
FetchWorkspace(ProjectWorkspaceProgress),
|
||||||
|
@ -549,14 +555,29 @@ impl GlobalState {
|
||||||
}
|
}
|
||||||
// Diagnostics are triggered by the user typing
|
// Diagnostics are triggered by the user typing
|
||||||
// so we run them on a latency sensitive thread.
|
// 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();
|
let subscriptions = subscriptions.clone();
|
||||||
move || {
|
move |sender| {
|
||||||
Task::Diagnostics(
|
let diags = fetch_native_diagnostics(
|
||||||
generation,
|
&snapshot,
|
||||||
fetch_native_diagnostics(snapshot, subscriptions, slice),
|
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;
|
start = end;
|
||||||
|
@ -644,10 +665,8 @@ impl GlobalState {
|
||||||
// Only retry requests that haven't been cancelled. Otherwise we do unnecessary work.
|
// 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(req) if !self.is_completed(&req) => self.on_request(req),
|
||||||
Task::Retry(_) => (),
|
Task::Retry(_) => (),
|
||||||
Task::Diagnostics(generation, diagnostics_per_file) => {
|
Task::Diagnostics(kind) => {
|
||||||
for (file_id, diagnostics) in diagnostics_per_file {
|
self.diagnostics.set_native_diagnostics(kind);
|
||||||
self.diagnostics.set_native_diagnostics(generation, file_id, diagnostics)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Task::PrimeCaches(progress) => match progress {
|
Task::PrimeCaches(progress) => match progress {
|
||||||
PrimeCachesProgress::Begin => prime_caches_progress.push(progress),
|
PrimeCachesProgress::Begin => prime_caches_progress.push(progress),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue