Skip to content

Commit 22f7c08

Browse files
committed
Auto merge of rust-lang#17775 - ShoyuVanilla:segregate-diags, r=Veykril
perf: Segregate syntax and semantic diagnostics Closes rust-lang#17731
2 parents 56a7922 + 80c8786 commit 22f7c08

File tree

9 files changed

+255
-146
lines changed

9 files changed

+255
-146
lines changed

src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs

+61-17
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ use syntax::{
9696
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
9797
pub enum DiagnosticCode {
9898
RustcHardError(&'static str),
99+
SyntaxError,
99100
RustcLint(&'static str),
100101
Clippy(&'static str),
101102
Ra(&'static str, Severity),
@@ -107,6 +108,9 @@ impl DiagnosticCode {
107108
DiagnosticCode::RustcHardError(e) => {
108109
format!("https://doc.rust-lang.org/stable/error_codes/{e}.html")
109110
}
111+
DiagnosticCode::SyntaxError => {
112+
String::from("https://doc.rust-lang.org/stable/reference/")
113+
}
110114
DiagnosticCode::RustcLint(e) => {
111115
format!("https://doc.rust-lang.org/rustc/?search={e}")
112116
}
@@ -125,6 +129,7 @@ impl DiagnosticCode {
125129
| DiagnosticCode::RustcLint(r)
126130
| DiagnosticCode::Clippy(r)
127131
| DiagnosticCode::Ra(r, _) => r,
132+
DiagnosticCode::SyntaxError => "syntax-error",
128133
}
129134
}
130135
}
@@ -154,7 +159,7 @@ impl Diagnostic {
154159
message,
155160
range: range.into(),
156161
severity: match code {
157-
DiagnosticCode::RustcHardError(_) => Severity::Error,
162+
DiagnosticCode::RustcHardError(_) | DiagnosticCode::SyntaxError => Severity::Error,
158163
// FIXME: Rustc lints are not always warning, but the ones that are currently implemented are all warnings.
159164
DiagnosticCode::RustcLint(_) => Severity::Warning,
160165
// FIXME: We can make this configurable, and if the user uses `cargo clippy` on flycheck, we can
@@ -297,31 +302,54 @@ impl DiagnosticsContext<'_> {
297302
}
298303
}
299304

300-
/// Request diagnostics for the given [`FileId`]. The produced diagnostics may point to other files
305+
/// Request parser level diagnostics for the given [`FileId`].
306+
pub fn syntax_diagnostics(
307+
db: &RootDatabase,
308+
config: &DiagnosticsConfig,
309+
file_id: FileId,
310+
) -> Vec<Diagnostic> {
311+
let _p = tracing::info_span!("syntax_diagnostics").entered();
312+
313+
if config.disabled.contains("syntax-error") {
314+
return Vec::new();
315+
}
316+
317+
let sema = Semantics::new(db);
318+
let file_id = sema
319+
.attach_first_edition(file_id)
320+
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
321+
322+
// [#3434] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
323+
db.parse_errors(file_id)
324+
.as_deref()
325+
.into_iter()
326+
.flatten()
327+
.take(128)
328+
.map(|err| {
329+
Diagnostic::new(
330+
DiagnosticCode::SyntaxError,
331+
format!("Syntax Error: {err}"),
332+
FileRange { file_id: file_id.into(), range: err.range() },
333+
)
334+
})
335+
.collect()
336+
}
337+
338+
/// Request semantic diagnostics for the given [`FileId`]. The produced diagnostics may point to other files
301339
/// due to macros.
302-
pub fn diagnostics(
340+
pub fn semantic_diagnostics(
303341
db: &RootDatabase,
304342
config: &DiagnosticsConfig,
305343
resolve: &AssistResolveStrategy,
306344
file_id: FileId,
307345
) -> Vec<Diagnostic> {
308-
let _p = tracing::info_span!("diagnostics").entered();
346+
let _p = tracing::info_span!("semantic_diagnostics").entered();
309347
let sema = Semantics::new(db);
310348
let file_id = sema
311349
.attach_first_edition(file_id)
312350
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
313351
let mut res = Vec::new();
314352

315-
// [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
316-
res.extend(db.parse_errors(file_id).as_deref().into_iter().flatten().take(128).map(|err| {
317-
Diagnostic::new(
318-
DiagnosticCode::RustcHardError("syntax-error"),
319-
format!("Syntax Error: {err}"),
320-
FileRange { file_id: file_id.into(), range: err.range() },
321-
)
322-
}));
323-
let parse_errors = res.len();
324-
325353
let parse = sema.parse(file_id);
326354

327355
// FIXME: This iterates the entire file which is a rather expensive operation.
@@ -341,8 +369,11 @@ pub fn diagnostics(
341369
match module {
342370
// A bunch of parse errors in a file indicate some bigger structural parse changes in the
343371
// file, so we skip semantic diagnostics so we can show these faster.
344-
Some(m) if parse_errors < 16 => m.diagnostics(db, &mut diags, config.style_lints),
345-
Some(_) => (),
372+
Some(m) => {
373+
if !db.parse_errors(file_id).as_deref().is_some_and(|es| es.len() >= 16) {
374+
m.diagnostics(db, &mut diags, config.style_lints);
375+
}
376+
}
346377
None => handlers::unlinked_file::unlinked_file(&ctx, &mut res, file_id.file_id()),
347378
}
348379

@@ -363,7 +394,7 @@ pub fn diagnostics(
363394
res.extend(d.errors.iter().take(16).map(|err| {
364395
{
365396
Diagnostic::new(
366-
DiagnosticCode::RustcHardError("syntax-error"),
397+
DiagnosticCode::SyntaxError,
367398
format!("Syntax Error in Expansion: {err}"),
368399
ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
369400
)
@@ -464,6 +495,19 @@ pub fn diagnostics(
464495
res
465496
}
466497

498+
/// Request both syntax and semantic diagnostics for the given [`FileId`].
499+
pub fn full_diagnostics(
500+
db: &RootDatabase,
501+
config: &DiagnosticsConfig,
502+
resolve: &AssistResolveStrategy,
503+
file_id: FileId,
504+
) -> Vec<Diagnostic> {
505+
let mut res = syntax_diagnostics(db, config, file_id);
506+
let sema = semantic_diagnostics(db, config, resolve, file_id);
507+
res.extend(sema);
508+
res
509+
}
510+
467511
// `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros
468512

469513
static RUSTC_LINT_GROUPS_DICT: Lazy<FxHashMap<&str, Vec<&str>>> =

src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs

+77-69
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ fn check_nth_fix_with_config(
5959
let after = trim_indent(ra_fixture_after);
6060

6161
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
62-
let diagnostic =
63-
super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_position.file_id.into())
64-
.pop()
65-
.expect("no diagnostics");
62+
let diagnostic = super::full_diagnostics(
63+
&db,
64+
&config,
65+
&AssistResolveStrategy::All,
66+
file_position.file_id.into(),
67+
)
68+
.pop()
69+
.expect("no diagnostics");
6670
let fix = &diagnostic
6771
.fixes
6872
.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) {
102106
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
103107
let mut conf = DiagnosticsConfig::test_sample();
104108
conf.expr_fill_default = ExprFillDefaultMode::Default;
105-
let fix =
106-
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id.into())
107-
.into_iter()
108-
.find(|d| {
109-
d.fixes
110-
.as_ref()
111-
.and_then(|fixes| {
112-
fixes.iter().find(|fix| {
113-
if !fix.target.contains_inclusive(file_position.offset) {
114-
return false;
115-
}
116-
let actual = {
117-
let source_change = fix.source_change.as_ref().unwrap();
118-
let file_id =
119-
*source_change.source_file_edits.keys().next().unwrap();
120-
let mut actual = db.file_text(file_id).to_string();
109+
let fix = super::full_diagnostics(
110+
&db,
111+
&conf,
112+
&AssistResolveStrategy::All,
113+
file_position.file_id.into(),
114+
)
115+
.into_iter()
116+
.find(|d| {
117+
d.fixes
118+
.as_ref()
119+
.and_then(|fixes| {
120+
fixes.iter().find(|fix| {
121+
if !fix.target.contains_inclusive(file_position.offset) {
122+
return false;
123+
}
124+
let actual = {
125+
let source_change = fix.source_change.as_ref().unwrap();
126+
let file_id = *source_change.source_file_edits.keys().next().unwrap();
127+
let mut actual = db.file_text(file_id).to_string();
121128

122-
for (edit, snippet_edit) in source_change.source_file_edits.values()
123-
{
124-
edit.apply(&mut actual);
125-
if let Some(snippet_edit) = snippet_edit {
126-
snippet_edit.apply(&mut actual);
127-
}
128-
}
129-
actual
130-
};
131-
after == actual
132-
})
133-
})
134-
.is_some()
135-
});
129+
for (edit, snippet_edit) in source_change.source_file_edits.values() {
130+
edit.apply(&mut actual);
131+
if let Some(snippet_edit) = snippet_edit {
132+
snippet_edit.apply(&mut actual);
133+
}
134+
}
135+
actual
136+
};
137+
after == actual
138+
})
139+
})
140+
.is_some()
141+
});
136142
assert!(fix.is_some(), "no diagnostic with desired fix");
137143
}
138144

@@ -144,46 +150,48 @@ pub(crate) fn check_has_single_fix(ra_fixture_before: &str, ra_fixture_after: &s
144150
let mut conf = DiagnosticsConfig::test_sample();
145151
conf.expr_fill_default = ExprFillDefaultMode::Default;
146152
let mut n_fixes = 0;
147-
let fix =
148-
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id.into())
149-
.into_iter()
150-
.find(|d| {
151-
d.fixes
152-
.as_ref()
153-
.and_then(|fixes| {
154-
n_fixes += fixes.len();
155-
fixes.iter().find(|fix| {
156-
if !fix.target.contains_inclusive(file_position.offset) {
157-
return false;
158-
}
159-
let actual = {
160-
let source_change = fix.source_change.as_ref().unwrap();
161-
let file_id =
162-
*source_change.source_file_edits.keys().next().unwrap();
163-
let mut actual = db.file_text(file_id).to_string();
153+
let fix = super::full_diagnostics(
154+
&db,
155+
&conf,
156+
&AssistResolveStrategy::All,
157+
file_position.file_id.into(),
158+
)
159+
.into_iter()
160+
.find(|d| {
161+
d.fixes
162+
.as_ref()
163+
.and_then(|fixes| {
164+
n_fixes += fixes.len();
165+
fixes.iter().find(|fix| {
166+
if !fix.target.contains_inclusive(file_position.offset) {
167+
return false;
168+
}
169+
let actual = {
170+
let source_change = fix.source_change.as_ref().unwrap();
171+
let file_id = *source_change.source_file_edits.keys().next().unwrap();
172+
let mut actual = db.file_text(file_id).to_string();
164173

165-
for (edit, snippet_edit) in source_change.source_file_edits.values()
166-
{
167-
edit.apply(&mut actual);
168-
if let Some(snippet_edit) = snippet_edit {
169-
snippet_edit.apply(&mut actual);
170-
}
171-
}
172-
actual
173-
};
174-
after == actual
175-
})
176-
})
177-
.is_some()
178-
});
174+
for (edit, snippet_edit) in source_change.source_file_edits.values() {
175+
edit.apply(&mut actual);
176+
if let Some(snippet_edit) = snippet_edit {
177+
snippet_edit.apply(&mut actual);
178+
}
179+
}
180+
actual
181+
};
182+
after == actual
183+
})
184+
})
185+
.is_some()
186+
});
179187
assert!(fix.is_some(), "no diagnostic with desired fix");
180188
assert!(n_fixes == 1, "Too many fixes suggested");
181189
}
182190

183191
/// Checks that there's a diagnostic *without* fix at `$0`.
184192
pub(crate) fn check_no_fix(ra_fixture: &str) {
185193
let (db, file_position) = RootDatabase::with_position(ra_fixture);
186-
let diagnostic = super::diagnostics(
194+
let diagnostic = super::full_diagnostics(
187195
&db,
188196
&DiagnosticsConfig::test_sample(),
189197
&AssistResolveStrategy::All,
@@ -215,7 +223,7 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
215223
.iter()
216224
.copied()
217225
.flat_map(|file_id| {
218-
super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id.into())
226+
super::full_diagnostics(&db, &config, &AssistResolveStrategy::All, file_id.into())
219227
.into_iter()
220228
.map(|d| {
221229
let mut annotation = String::new();
@@ -277,10 +285,10 @@ fn test_disabled_diagnostics() {
277285
let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
278286
let file_id = file_id.into();
279287

280-
let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
288+
let diagnostics = super::full_diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
281289
assert!(diagnostics.is_empty());
282290

283-
let diagnostics = super::diagnostics(
291+
let diagnostics = super::full_diagnostics(
284292
&db,
285293
&DiagnosticsConfig::test_sample(),
286294
&AssistResolveStrategy::All,

src/tools/rust-analyzer/crates/ide/src/lib.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -672,14 +672,33 @@ impl Analysis {
672672
.unwrap_or_default())
673673
}
674674

675-
/// Computes the set of diagnostics for the given file.
676-
pub fn diagnostics(
675+
/// Computes the set of parser level diagnostics for the given file.
676+
pub fn syntax_diagnostics(
677+
&self,
678+
config: &DiagnosticsConfig,
679+
file_id: FileId,
680+
) -> Cancellable<Vec<Diagnostic>> {
681+
self.with_db(|db| ide_diagnostics::syntax_diagnostics(db, config, file_id))
682+
}
683+
684+
/// Computes the set of semantic diagnostics for the given file.
685+
pub fn semantic_diagnostics(
686+
&self,
687+
config: &DiagnosticsConfig,
688+
resolve: AssistResolveStrategy,
689+
file_id: FileId,
690+
) -> Cancellable<Vec<Diagnostic>> {
691+
self.with_db(|db| ide_diagnostics::semantic_diagnostics(db, config, &resolve, file_id))
692+
}
693+
694+
/// Computes the set of both syntax and semantic diagnostics for the given file.
695+
pub fn full_diagnostics(
677696
&self,
678697
config: &DiagnosticsConfig,
679698
resolve: AssistResolveStrategy,
680699
file_id: FileId,
681700
) -> Cancellable<Vec<Diagnostic>> {
682-
self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id))
701+
self.with_db(|db| ide_diagnostics::full_diagnostics(db, config, &resolve, file_id))
683702
}
684703

685704
/// Convenience function to return assists + quick fixes for diagnostics
@@ -697,7 +716,7 @@ impl Analysis {
697716

698717
self.with_db(|db| {
699718
let diagnostic_assists = if diagnostics_config.enabled && include_fixes {
700-
ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
719+
ide_diagnostics::full_diagnostics(db, diagnostics_config, &resolve, frange.file_id)
701720
.into_iter()
702721
.flat_map(|it| it.fixes.unwrap_or_default())
703722
.filter(|it| it.target.intersect(frange.range).is_some())

src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ impl flags::AnalysisStats {
976976
let mut sw = self.stop_watch();
977977

978978
for &file_id in &file_ids {
979-
_ = analysis.diagnostics(
979+
_ = analysis.full_diagnostics(
980980
&DiagnosticsConfig {
981981
enabled: true,
982982
proc_macros_enabled: true,

0 commit comments

Comments
 (0)