aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-04-13 10:09:24 +0100
committerGitHub <[email protected]>2021-04-13 10:09:24 +0100
commit6081b437cc842f8885c26636bef8af4cbc5483e3 (patch)
treed608b8a7edd630a8b028c117c615ece9a48c4186 /crates/ide
parentfe29a9e837c6e9699185ccae55e83902b4f6ef6a (diff)
parent06a633ff421b764428bb946ced914e59532fe13f (diff)
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]>
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/diagnostics.rs60
-rw-r--r--crates/ide/src/diagnostics/fixes.rs38
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs2
-rw-r--r--crates/ide/src/lib.rs32
4 files changed, 96 insertions, 36 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 4f0b4a62e..9a883acb9 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -84,6 +84,7 @@ pub struct DiagnosticsConfig {
84pub(crate) fn diagnostics( 84pub(crate) fn diagnostics(
85 db: &RootDatabase, 85 db: &RootDatabase,
86 config: &DiagnosticsConfig, 86 config: &DiagnosticsConfig,
87 resolve: bool,
87 file_id: FileId, 88 file_id: FileId,
88) -> Vec<Diagnostic> { 89) -> Vec<Diagnostic> {
89 let _p = profile::span("diagnostics"); 90 let _p = profile::span("diagnostics");
@@ -107,25 +108,25 @@ pub(crate) fn diagnostics(
107 let res = RefCell::new(res); 108 let res = RefCell::new(res);
108 let sink_builder = DiagnosticSinkBuilder::new() 109 let sink_builder = DiagnosticSinkBuilder::new()
109 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 110 .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));
111 }) 112 })
112 .on::<hir::diagnostics::MissingFields, _>(|d| { 113 .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));
114 }) 115 })
115 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { 116 .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));
117 }) 118 })
118 .on::<hir::diagnostics::NoSuchField, _>(|d| { 119 .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));
120 }) 121 })
121 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { 122 .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));
123 }) 124 })
124 .on::<hir::diagnostics::IncorrectCase, _>(|d| { 125 .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));
126 }) 127 })
127 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { 128 .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));
129 }) 130 })
130 .on::<hir::diagnostics::InactiveCode, _>(|d| { 131 .on::<hir::diagnostics::InactiveCode, _>(|d| {
131 // If there's inactive code somewhere in a macro, don't propagate to the call-site. 132 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
@@ -152,7 +153,7 @@ pub(crate) fn diagnostics(
152 // Override severity and mark as unused. 153 // Override severity and mark as unused.
153 res.borrow_mut().push( 154 res.borrow_mut().push(
154 Diagnostic::hint(range, d.message()) 155 Diagnostic::hint(range, d.message())
155 .with_fix(d.fix(&sema)) 156 .with_fix(d.fix(&sema, resolve))
156 .with_code(Some(d.code())), 157 .with_code(Some(d.code())),
157 ); 158 );
158 }) 159 })
@@ -208,15 +209,23 @@ pub(crate) fn diagnostics(
208 res.into_inner() 209 res.into_inner()
209} 210}
210 211
211fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 212fn diagnostic_with_fix<D: DiagnosticWithFix>(
213 d: &D,
214 sema: &Semantics<RootDatabase>,
215 resolve: bool,
216) -> Diagnostic {
212 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) 217 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))
214 .with_code(Some(d.code())) 219 .with_code(Some(d.code()))
215} 220}
216 221
217fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 222fn warning_with_fix<D: DiagnosticWithFix>(
223 d: &D,
224 sema: &Semantics<RootDatabase>,
225 resolve: bool,
226) -> Diagnostic {
218 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) 227 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))
220 .with_code(Some(d.code())) 229 .with_code(Some(d.code()))
221} 230}
222 231
@@ -271,13 +280,19 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
271} 280}
272 281
273fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { 282fn 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
288fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
274 assert!(!id.contains(' ')); 289 assert!(!id.contains(' '));
275 Assist { 290 Assist {
276 id: AssistId(id, AssistKind::QuickFix), 291 id: AssistId(id, AssistKind::QuickFix),
277 label: Label::new(label), 292 label: Label::new(label),
278 group: None, 293 group: None,
279 target, 294 target,
280 source_change: Some(source_change), 295 source_change: None,
281 } 296 }
282} 297}
283 298
@@ -299,7 +314,7 @@ mod tests {
299 314
300 let (analysis, file_position) = fixture::position(ra_fixture_before); 315 let (analysis, file_position) = fixture::position(ra_fixture_before);
301 let diagnostic = analysis 316 let diagnostic = analysis
302 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 317 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
303 .unwrap() 318 .unwrap()
304 .pop() 319 .pop()
305 .unwrap(); 320 .unwrap();
@@ -328,7 +343,7 @@ mod tests {
328 fn check_no_fix(ra_fixture: &str) { 343 fn check_no_fix(ra_fixture: &str) {
329 let (analysis, file_position) = fixture::position(ra_fixture); 344 let (analysis, file_position) = fixture::position(ra_fixture);
330 let diagnostic = analysis 345 let diagnostic = analysis
331 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 346 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
332 .unwrap() 347 .unwrap()
333 .pop() 348 .pop()
334 .unwrap(); 349 .unwrap();
@@ -342,7 +357,7 @@ mod tests {
342 let diagnostics = files 357 let diagnostics = files
343 .into_iter() 358 .into_iter()
344 .flat_map(|file_id| { 359 .flat_map(|file_id| {
345 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() 360 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap()
346 }) 361 })
347 .collect::<Vec<_>>(); 362 .collect::<Vec<_>>();
348 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 363 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
@@ -350,7 +365,8 @@ mod tests {
350 365
351 fn check_expect(ra_fixture: &str, expect: Expect) { 366 fn check_expect(ra_fixture: &str, expect: Expect) {
352 let (analysis, file_id) = fixture::file(ra_fixture); 367 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();
354 expect.assert_debug_eq(&diagnostics) 370 expect.assert_debug_eq(&diagnostics)
355 } 371 }
356 372
@@ -895,10 +911,11 @@ struct Foo {
895 911
896 let (analysis, file_id) = fixture::file(r#"mod foo;"#); 912 let (analysis, file_id) = fixture::file(r#"mod foo;"#);
897 913
898 let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); 914 let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap();
899 assert!(diagnostics.is_empty()); 915 assert!(diagnostics.is_empty());
900 916
901 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 917 let diagnostics =
918 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
902 assert!(!diagnostics.is_empty()); 919 assert!(!diagnostics.is_empty());
903 } 920 }
904 921
@@ -1004,8 +1021,9 @@ impl TestStruct {
1004 let expected = r#"fn foo() {}"#; 1021 let expected = r#"fn foo() {}"#;
1005 1022
1006 let (analysis, file_position) = fixture::position(input); 1023 let (analysis, file_position) = fixture::position(input);
1007 let diagnostics = 1024 let diagnostics = analysis
1008 analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); 1025 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
1026 .unwrap();
1009 assert_eq!(diagnostics.len(), 1); 1027 assert_eq!(diagnostics.len(), 1);
1010 1028
1011 check_fix(input, expected); 1029 check_fix(input, expected);
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 69cf5288c..7be8b3459 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -20,17 +20,26 @@ use syntax::{
20}; 20};
21use text_edit::TextEdit; 21use text_edit::TextEdit;
22 22
23use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, FilePosition}; 23use crate::{
24 diagnostics::{fix, unresolved_fix},
25 references::rename::rename_with_semantics,
26 Assist, FilePosition,
27};
24 28
25/// A [Diagnostic] that potentially has a fix available. 29/// A [Diagnostic] that potentially has a fix available.
26/// 30///
27/// [Diagnostic]: hir::diagnostics::Diagnostic 31/// [Diagnostic]: hir::diagnostics::Diagnostic
28pub(crate) trait DiagnosticWithFix: Diagnostic { 32pub(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>;
30} 39}
31 40
32impl DiagnosticWithFix for UnresolvedModule { 41impl DiagnosticWithFix for UnresolvedModule {
33 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 42 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
34 let root = sema.db.parse_or_expand(self.file)?; 43 let root = sema.db.parse_or_expand(self.file)?;
35 let unresolved_module = self.decl.to_node(&root); 44 let unresolved_module = self.decl.to_node(&root);
36 Some(fix( 45 Some(fix(
@@ -50,7 +59,7 @@ impl DiagnosticWithFix for UnresolvedModule {
50} 59}
51 60
52impl DiagnosticWithFix for NoSuchField { 61impl DiagnosticWithFix for NoSuchField {
53 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 62 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
54 let root = sema.db.parse_or_expand(self.file)?; 63 let root = sema.db.parse_or_expand(self.file)?;
55 missing_record_expr_field_fix( 64 missing_record_expr_field_fix(
56 &sema, 65 &sema,
@@ -61,7 +70,7 @@ impl DiagnosticWithFix for NoSuchField {
61} 70}
62 71
63impl DiagnosticWithFix for MissingFields { 72impl DiagnosticWithFix for MissingFields {
64 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 73 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
65 // Note that although we could add a diagnostics to 74 // Note that although we could add a diagnostics to
66 // fill the missing tuple field, e.g : 75 // fill the missing tuple field, e.g :
67 // `struct A(usize);` 76 // `struct A(usize);`
@@ -97,7 +106,7 @@ impl DiagnosticWithFix for MissingFields {
97} 106}
98 107
99impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { 108impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
100 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 109 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
101 let root = sema.db.parse_or_expand(self.file)?; 110 let root = sema.db.parse_or_expand(self.file)?;
102 let tail_expr = self.expr.to_node(&root); 111 let tail_expr = self.expr.to_node(&root);
103 let tail_expr_range = tail_expr.syntax().text_range(); 112 let tail_expr_range = tail_expr.syntax().text_range();
@@ -110,7 +119,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
110} 119}
111 120
112impl DiagnosticWithFix for RemoveThisSemicolon { 121impl DiagnosticWithFix for RemoveThisSemicolon {
113 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 122 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
114 let root = sema.db.parse_or_expand(self.file)?; 123 let root = sema.db.parse_or_expand(self.file)?;
115 124
116 let semicolon = self 125 let semicolon = self
@@ -130,7 +139,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
130} 139}
131 140
132impl DiagnosticWithFix for IncorrectCase { 141impl DiagnosticWithFix for IncorrectCase {
133 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 142 fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> {
134 let root = sema.db.parse_or_expand(self.file)?; 143 let root = sema.db.parse_or_expand(self.file)?;
135 let name_node = self.ident.to_node(&root); 144 let name_node = self.ident.to_node(&root);
136 145
@@ -138,16 +147,19 @@ impl DiagnosticWithFix for IncorrectCase {
138 let frange = name_node.original_file_range(sema.db); 147 let frange = name_node.original_file_range(sema.db);
139 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; 148 let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
140 149
141 let rename_changes =
142 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
143
144 let label = format!("Rename to {}", self.suggested_text); 150 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)
146 } 158 }
147} 159}
148 160
149impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 161impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
150 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 162 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
151 let root = sema.db.parse_or_expand(self.file)?; 163 let root = sema.db.parse_or_expand(self.file)?;
152 let next_expr = self.next_expr.to_node(&root); 164 let next_expr = self.next_expr.to_node(&root);
153 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; 165 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index 5482b7287..7d39f4fbe 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -50,7 +50,7 @@ impl Diagnostic for UnlinkedFile {
50} 50}
51 51
52impl DiagnosticWithFix for UnlinkedFile { 52impl 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> {
54 // If there's an existing module that could add a `mod` item to include the unlinked file, 54 // If there's an existing module that could add a `mod` item to include the unlinked file,
55 // suggest that as a fix. 55 // suggest that as a fix.
56 56
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 0615b26d3..d481be09d 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -526,9 +526,39 @@ impl Analysis {
526 pub fn diagnostics( 526 pub fn diagnostics(
527 &self, 527 &self,
528 config: &DiagnosticsConfig, 528 config: &DiagnosticsConfig,
529 resolve: bool,
529 file_id: FileId, 530 file_id: FileId,
530 ) -> Cancelable<Vec<Diagnostic>> { 531 ) -> 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 })
532 } 562 }
533 563
534 /// Returns the edit required to rename reference at the position to the new 564 /// Returns the edit required to rename reference at the position to the new