Skip to content

Commit 6081b43

Browse files
bors[bot]matklad
andauthored
Merge #8498
8498: feat: improve performance by delaying computation of fixes for diagnostics r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents fe29a9e + 06a633f commit 6081b43

File tree

7 files changed

+115
-61
lines changed

7 files changed

+115
-61
lines changed

crates/ide/src/diagnostics.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub struct DiagnosticsConfig {
8484
pub(crate) fn diagnostics(
8585
db: &RootDatabase,
8686
config: &DiagnosticsConfig,
87+
resolve: bool,
8788
file_id: FileId,
8889
) -> Vec<Diagnostic> {
8990
let _p = profile::span("diagnostics");
@@ -107,25 +108,25 @@ pub(crate) fn diagnostics(
107108
let res = RefCell::new(res);
108109
let sink_builder = DiagnosticSinkBuilder::new()
109110
.on::<hir::diagnostics::UnresolvedModule, _>(|d| {
110-
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
111+
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
111112
})
112113
.on::<hir::diagnostics::MissingFields, _>(|d| {
113-
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
114+
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
114115
})
115116
.on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
116-
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
117+
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
117118
})
118119
.on::<hir::diagnostics::NoSuchField, _>(|d| {
119-
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
120+
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
120121
})
121122
.on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
122-
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
123+
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
123124
})
124125
.on::<hir::diagnostics::IncorrectCase, _>(|d| {
125-
res.borrow_mut().push(warning_with_fix(d, &sema));
126+
res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
126127
})
127128
.on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
128-
res.borrow_mut().push(warning_with_fix(d, &sema));
129+
res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
129130
})
130131
.on::<hir::diagnostics::InactiveCode, _>(|d| {
131132
// If there's inactive code somewhere in a macro, don't propagate to the call-site.
@@ -152,7 +153,7 @@ pub(crate) fn diagnostics(
152153
// Override severity and mark as unused.
153154
res.borrow_mut().push(
154155
Diagnostic::hint(range, d.message())
155-
.with_fix(d.fix(&sema))
156+
.with_fix(d.fix(&sema, resolve))
156157
.with_code(Some(d.code())),
157158
);
158159
})
@@ -208,15 +209,23 @@ pub(crate) fn diagnostics(
208209
res.into_inner()
209210
}
210211

211-
fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
212+
fn diagnostic_with_fix<D: DiagnosticWithFix>(
213+
d: &D,
214+
sema: &Semantics<RootDatabase>,
215+
resolve: bool,
216+
) -> Diagnostic {
212217
Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
213-
.with_fix(d.fix(&sema))
218+
.with_fix(d.fix(&sema, resolve))
214219
.with_code(Some(d.code()))
215220
}
216221

217-
fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
222+
fn warning_with_fix<D: DiagnosticWithFix>(
223+
d: &D,
224+
sema: &Semantics<RootDatabase>,
225+
resolve: bool,
226+
) -> Diagnostic {
218227
Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
219-
.with_fix(d.fix(&sema))
228+
.with_fix(d.fix(&sema, resolve))
220229
.with_code(Some(d.code()))
221230
}
222231

@@ -271,13 +280,19 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
271280
}
272281

273282
fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
283+
let mut res = unresolved_fix(id, label, target);
284+
res.source_change = Some(source_change);
285+
res
286+
}
287+
288+
fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
274289
assert!(!id.contains(' '));
275290
Assist {
276291
id: AssistId(id, AssistKind::QuickFix),
277292
label: Label::new(label),
278293
group: None,
279294
target,
280-
source_change: Some(source_change),
295+
source_change: None,
281296
}
282297
}
283298

@@ -299,7 +314,7 @@ mod tests {
299314

300315
let (analysis, file_position) = fixture::position(ra_fixture_before);
301316
let diagnostic = analysis
302-
.diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
317+
.diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
303318
.unwrap()
304319
.pop()
305320
.unwrap();
@@ -328,7 +343,7 @@ mod tests {
328343
fn check_no_fix(ra_fixture: &str) {
329344
let (analysis, file_position) = fixture::position(ra_fixture);
330345
let diagnostic = analysis
331-
.diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
346+
.diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
332347
.unwrap()
333348
.pop()
334349
.unwrap();
@@ -342,15 +357,16 @@ mod tests {
342357
let diagnostics = files
343358
.into_iter()
344359
.flat_map(|file_id| {
345-
analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
360+
analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap()
346361
})
347362
.collect::<Vec<_>>();
348363
assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
349364
}
350365

351366
fn check_expect(ra_fixture: &str, expect: Expect) {
352367
let (analysis, file_id) = fixture::file(ra_fixture);
353-
let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
368+
let diagnostics =
369+
analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
354370
expect.assert_debug_eq(&diagnostics)
355371
}
356372

@@ -895,10 +911,11 @@ struct Foo {
895911

896912
let (analysis, file_id) = fixture::file(r#"mod foo;"#);
897913

898-
let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
914+
let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap();
899915
assert!(diagnostics.is_empty());
900916

901-
let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
917+
let diagnostics =
918+
analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
902919
assert!(!diagnostics.is_empty());
903920
}
904921

@@ -1004,8 +1021,9 @@ impl TestStruct {
10041021
let expected = r#"fn foo() {}"#;
10051022

10061023
let (analysis, file_position) = fixture::position(input);
1007-
let diagnostics =
1008-
analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap();
1024+
let diagnostics = analysis
1025+
.diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
1026+
.unwrap();
10091027
assert_eq!(diagnostics.len(), 1);
10101028

10111029
check_fix(input, expected);

crates/ide/src/diagnostics/fixes.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,26 @@ use syntax::{
2020
};
2121
use text_edit::TextEdit;
2222

23-
use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, FilePosition};
23+
use crate::{
24+
diagnostics::{fix, unresolved_fix},
25+
references::rename::rename_with_semantics,
26+
Assist, FilePosition,
27+
};
2428

2529
/// A [Diagnostic] that potentially has a fix available.
2630
///
2731
/// [Diagnostic]: hir::diagnostics::Diagnostic
2832
pub(crate) trait DiagnosticWithFix: Diagnostic {
29-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist>;
33+
/// `resolve` determines if the diagnostic should fill in the `edit` field
34+
/// of the assist.
35+
///
36+
/// If `resolve` is false, the edit will be computed later, on demand, and
37+
/// can be omitted.
38+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>;
3039
}
3140

3241
impl DiagnosticWithFix for UnresolvedModule {
33-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
42+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
3443
let root = sema.db.parse_or_expand(self.file)?;
3544
let unresolved_module = self.decl.to_node(&root);
3645
Some(fix(
@@ -50,7 +59,7 @@ impl DiagnosticWithFix for UnresolvedModule {
5059
}
5160

5261
impl DiagnosticWithFix for NoSuchField {
53-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
62+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
5463
let root = sema.db.parse_or_expand(self.file)?;
5564
missing_record_expr_field_fix(
5665
&sema,
@@ -61,7 +70,7 @@ impl DiagnosticWithFix for NoSuchField {
6170
}
6271

6372
impl DiagnosticWithFix for MissingFields {
64-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
73+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
6574
// Note that although we could add a diagnostics to
6675
// fill the missing tuple field, e.g :
6776
// `struct A(usize);`
@@ -97,7 +106,7 @@ impl DiagnosticWithFix for MissingFields {
97106
}
98107

99108
impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
100-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
109+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
101110
let root = sema.db.parse_or_expand(self.file)?;
102111
let tail_expr = self.expr.to_node(&root);
103112
let tail_expr_range = tail_expr.syntax().text_range();
@@ -110,7 +119,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
110119
}
111120

112121
impl DiagnosticWithFix for RemoveThisSemicolon {
113-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
122+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
114123
let root = sema.db.parse_or_expand(self.file)?;
115124

116125
let semicolon = self
@@ -130,24 +139,27 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
130139
}
131140

132141
impl DiagnosticWithFix for IncorrectCase {
133-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
142+
fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> {
134143
let root = sema.db.parse_or_expand(self.file)?;
135144
let name_node = self.ident.to_node(&root);
136145

137146
let name_node = InFile::new(self.file, name_node.syntax());
138147
let frange = name_node.original_file_range(sema.db);
139148
let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
140149

141-
let rename_changes =
142-
rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
143-
144150
let label = format!("Rename to {}", self.suggested_text);
145-
Some(fix("change_case", &label, rename_changes, frange.range))
151+
let mut res = unresolved_fix("change_case", &label, frange.range);
152+
if resolve {
153+
let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
154+
res.source_change = Some(source_change.ok().unwrap_or_default());
155+
}
156+
157+
Some(res)
146158
}
147159
}
148160

149161
impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
150-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
162+
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
151163
let root = sema.db.parse_or_expand(self.file)?;
152164
let next_expr = self.next_expr.to_node(&root);
153165
let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;

crates/ide/src/diagnostics/unlinked_file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl Diagnostic for UnlinkedFile {
5050
}
5151

5252
impl DiagnosticWithFix for UnlinkedFile {
53-
fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Assist> {
53+
fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
5454
// If there's an existing module that could add a `mod` item to include the unlinked file,
5555
// suggest that as a fix.
5656

crates/ide/src/lib.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,9 +526,39 @@ impl Analysis {
526526
pub fn diagnostics(
527527
&self,
528528
config: &DiagnosticsConfig,
529+
resolve: bool,
529530
file_id: FileId,
530531
) -> Cancelable<Vec<Diagnostic>> {
531-
self.with_db(|db| diagnostics::diagnostics(db, config, file_id))
532+
self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id))
533+
}
534+
535+
/// Convenience function to return assists + quick fixes for diagnostics
536+
pub fn assists_with_fixes(
537+
&self,
538+
assist_config: &AssistConfig,
539+
diagnostics_config: &DiagnosticsConfig,
540+
resolve: bool,
541+
frange: FileRange,
542+
) -> Cancelable<Vec<Assist>> {
543+
let include_fixes = match &assist_config.allowed {
544+
Some(it) => it.iter().any(|&it| it == AssistKind::None || it == AssistKind::QuickFix),
545+
None => true,
546+
};
547+
548+
self.with_db(|db| {
549+
let mut res = Assist::get(db, assist_config, resolve, frange);
550+
ssr::add_ssr_assist(db, &mut res, resolve, frange);
551+
552+
if include_fixes {
553+
res.extend(
554+
diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id)
555+
.into_iter()
556+
.filter_map(|it| it.fix)
557+
.filter(|it| it.target.intersect(frange.range).is_some()),
558+
);
559+
}
560+
res
561+
})
532562
}
533563

534564
/// Returns the edit required to rename reference at the position to the new

crates/rust-analyzer/src/cli/diagnostics.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ pub fn diagnostics(
5757
let crate_name =
5858
module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string();
5959
println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
60-
for diagnostic in analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
60+
for diagnostic in
61+
analysis.diagnostics(&DiagnosticsConfig::default(), false, file_id).unwrap()
6162
{
6263
if matches!(diagnostic.severity, Severity::Error) {
6364
found_error = true;

crates/rust-analyzer/src/handlers.rs

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::{
88
};
99

1010
use ide::{
11-
AnnotationConfig, AssistKind, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData,
12-
Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit,
11+
AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, Query,
12+
RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit,
1313
};
1414
use ide_db::SymbolKind;
1515
use itertools::Itertools;
@@ -1003,27 +1003,13 @@ pub(crate) fn handle_code_action(
10031003

10041004
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
10051005

1006-
let include_quick_fixes = match &assists_config.allowed {
1007-
Some(v) => v.iter().any(|it| it == &AssistKind::None || it == &AssistKind::QuickFix),
1008-
None => true,
1009-
};
10101006
let code_action_resolve_cap = snap.config.code_action_resolve();
1011-
1012-
let mut assists = Vec::new();
1013-
1014-
// Fixes from native diagnostics.
1015-
if include_quick_fixes {
1016-
let diagnostics = snap.analysis.diagnostics(&snap.config.diagnostics(), frange.file_id)?;
1017-
assists.extend(
1018-
diagnostics
1019-
.into_iter()
1020-
.filter_map(|d| d.fix)
1021-
.filter(|fix| fix.target.intersect(frange.range).is_some()),
1022-
)
1023-
}
1024-
1025-
// Assists proper.
1026-
assists.extend(snap.analysis.assists(&assists_config, !code_action_resolve_cap, frange)?);
1007+
let assists = snap.analysis.assists_with_fixes(
1008+
&assists_config,
1009+
&snap.config.diagnostics(),
1010+
!code_action_resolve_cap,
1011+
frange,
1012+
)?;
10271013
for (index, assist) in assists.into_iter().enumerate() {
10281014
let resolve_data =
10291015
if code_action_resolve_cap { Some((index, params.clone())) } else { None };
@@ -1066,7 +1052,13 @@ pub(crate) fn handle_code_action_resolve(
10661052
.only
10671053
.map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
10681054

1069-
let assists = snap.analysis.assists(&assists_config, true, frange)?;
1055+
let assists = snap.analysis.assists_with_fixes(
1056+
&assists_config,
1057+
&snap.config.diagnostics(),
1058+
true,
1059+
frange,
1060+
)?;
1061+
10701062
let (id, index) = split_once(&params.id, ':').unwrap();
10711063
let index = index.parse::<usize>().unwrap();
10721064
let assist = &assists[index];
@@ -1190,7 +1182,7 @@ pub(crate) fn publish_diagnostics(
11901182

11911183
let diagnostics: Vec<Diagnostic> = snap
11921184
.analysis
1193-
.diagnostics(&snap.config.diagnostics(), file_id)?
1185+
.diagnostics(&snap.config.diagnostics(), false, file_id)?
11941186
.into_iter()
11951187
.map(|d| Diagnostic {
11961188
range: to_proto::range(&line_index, d.range),

crates/rust-analyzer/tests/rust-analyzer/support.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ impl Server {
168168
self.send_notification(r)
169169
}
170170

171+
#[track_caller]
171172
pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value)
172173
where
173174
R: lsp_types::request::Request,

0 commit comments

Comments
 (0)