diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 43 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 51 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unlinked_file.rs | 7 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 39 | ||||
-rw-r--r-- | crates/ide/src/ssr.rs | 54 | ||||
-rw-r--r-- | crates/ide_assists/src/assist_context.rs | 49 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_function.rs | 33 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_impl.rs | 40 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 68 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 262 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/qualified_path.rs | 22 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 208 | ||||
-rw-r--r-- | crates/proc_macro_api/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/proc_macro_api/src/version.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/diagnostics.rs | 7 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 72 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 2 |
17 files changed, 718 insertions, 243 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 1c911a8b2..b14f908b7 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -15,6 +15,7 @@ use hir::{ | |||
15 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | 15 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, |
16 | InFile, Semantics, | 16 | InFile, Semantics, |
17 | }; | 17 | }; |
18 | use ide_assists::AssistResolveStrategy; | ||
18 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 19 | use ide_db::{base_db::SourceDatabase, RootDatabase}; |
19 | use itertools::Itertools; | 20 | use itertools::Itertools; |
20 | use rustc_hash::FxHashSet; | 21 | use rustc_hash::FxHashSet; |
@@ -84,7 +85,7 @@ pub struct DiagnosticsConfig { | |||
84 | pub(crate) fn diagnostics( | 85 | pub(crate) fn diagnostics( |
85 | db: &RootDatabase, | 86 | db: &RootDatabase, |
86 | config: &DiagnosticsConfig, | 87 | config: &DiagnosticsConfig, |
87 | resolve: bool, | 88 | resolve: &AssistResolveStrategy, |
88 | file_id: FileId, | 89 | file_id: FileId, |
89 | ) -> Vec<Diagnostic> { | 90 | ) -> Vec<Diagnostic> { |
90 | let _p = profile::span("diagnostics"); | 91 | let _p = profile::span("diagnostics"); |
@@ -212,7 +213,7 @@ pub(crate) fn diagnostics( | |||
212 | fn diagnostic_with_fix<D: DiagnosticWithFix>( | 213 | fn diagnostic_with_fix<D: DiagnosticWithFix>( |
213 | d: &D, | 214 | d: &D, |
214 | sema: &Semantics<RootDatabase>, | 215 | sema: &Semantics<RootDatabase>, |
215 | resolve: bool, | 216 | resolve: &AssistResolveStrategy, |
216 | ) -> Diagnostic { | 217 | ) -> Diagnostic { |
217 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 218 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
218 | .with_fix(d.fix(&sema, resolve)) | 219 | .with_fix(d.fix(&sema, resolve)) |
@@ -222,7 +223,7 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>( | |||
222 | fn warning_with_fix<D: DiagnosticWithFix>( | 223 | fn warning_with_fix<D: DiagnosticWithFix>( |
223 | d: &D, | 224 | d: &D, |
224 | sema: &Semantics<RootDatabase>, | 225 | sema: &Semantics<RootDatabase>, |
225 | resolve: bool, | 226 | resolve: &AssistResolveStrategy, |
226 | ) -> Diagnostic { | 227 | ) -> Diagnostic { |
227 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 228 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
228 | .with_fix(d.fix(&sema, resolve)) | 229 | .with_fix(d.fix(&sema, resolve)) |
@@ -299,6 +300,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | |||
299 | #[cfg(test)] | 300 | #[cfg(test)] |
300 | mod tests { | 301 | mod tests { |
301 | use expect_test::{expect, Expect}; | 302 | use expect_test::{expect, Expect}; |
303 | use ide_assists::AssistResolveStrategy; | ||
302 | use stdx::trim_indent; | 304 | use stdx::trim_indent; |
303 | use test_utils::assert_eq_text; | 305 | use test_utils::assert_eq_text; |
304 | 306 | ||
@@ -314,7 +316,11 @@ mod tests { | |||
314 | 316 | ||
315 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 317 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
316 | let diagnostic = analysis | 318 | let diagnostic = analysis |
317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) | 319 | .diagnostics( |
320 | &DiagnosticsConfig::default(), | ||
321 | AssistResolveStrategy::All, | ||
322 | file_position.file_id, | ||
323 | ) | ||
318 | .unwrap() | 324 | .unwrap() |
319 | .pop() | 325 | .pop() |
320 | .unwrap(); | 326 | .unwrap(); |
@@ -343,7 +349,11 @@ mod tests { | |||
343 | fn check_no_fix(ra_fixture: &str) { | 349 | fn check_no_fix(ra_fixture: &str) { |
344 | let (analysis, file_position) = fixture::position(ra_fixture); | 350 | let (analysis, file_position) = fixture::position(ra_fixture); |
345 | let diagnostic = analysis | 351 | let diagnostic = analysis |
346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) | 352 | .diagnostics( |
353 | &DiagnosticsConfig::default(), | ||
354 | AssistResolveStrategy::All, | ||
355 | file_position.file_id, | ||
356 | ) | ||
347 | .unwrap() | 357 | .unwrap() |
348 | .pop() | 358 | .pop() |
349 | .unwrap(); | 359 | .unwrap(); |
@@ -357,7 +367,9 @@ mod tests { | |||
357 | let diagnostics = files | 367 | let diagnostics = files |
358 | .into_iter() | 368 | .into_iter() |
359 | .flat_map(|file_id| { | 369 | .flat_map(|file_id| { |
360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() | 370 | analysis |
371 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
372 | .unwrap() | ||
361 | }) | 373 | }) |
362 | .collect::<Vec<_>>(); | 374 | .collect::<Vec<_>>(); |
363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 375 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -365,8 +377,9 @@ mod tests { | |||
365 | 377 | ||
366 | fn check_expect(ra_fixture: &str, expect: Expect) { | 378 | fn check_expect(ra_fixture: &str, expect: Expect) { |
367 | let (analysis, file_id) = fixture::file(ra_fixture); | 379 | let (analysis, file_id) = fixture::file(ra_fixture); |
368 | let diagnostics = | 380 | let diagnostics = analysis |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | 381 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) |
382 | .unwrap(); | ||
370 | expect.assert_debug_eq(&diagnostics) | 383 | expect.assert_debug_eq(&diagnostics) |
371 | } | 384 | } |
372 | 385 | ||
@@ -911,11 +924,13 @@ struct Foo { | |||
911 | 924 | ||
912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 925 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
913 | 926 | ||
914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); | 927 | let diagnostics = |
928 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | ||
915 | assert!(diagnostics.is_empty()); | 929 | assert!(diagnostics.is_empty()); |
916 | 930 | ||
917 | let diagnostics = | 931 | let diagnostics = analysis |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | 932 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) |
933 | .unwrap(); | ||
919 | assert!(!diagnostics.is_empty()); | 934 | assert!(!diagnostics.is_empty()); |
920 | } | 935 | } |
921 | 936 | ||
@@ -1022,7 +1037,11 @@ impl TestStruct { | |||
1022 | 1037 | ||
1023 | let (analysis, file_position) = fixture::position(input); | 1038 | let (analysis, file_position) = fixture::position(input); |
1024 | let diagnostics = analysis | 1039 | let diagnostics = analysis |
1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) | 1040 | .diagnostics( |
1041 | &DiagnosticsConfig::default(), | ||
1042 | AssistResolveStrategy::All, | ||
1043 | file_position.file_id, | ||
1044 | ) | ||
1026 | .unwrap(); | 1045 | .unwrap(); |
1027 | assert_eq!(diagnostics.len(), 1); | 1046 | assert_eq!(diagnostics.len(), 1); |
1028 | 1047 | ||
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 7be8b3459..15821500f 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -8,6 +8,7 @@ use hir::{ | |||
8 | }, | 8 | }, |
9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, | 9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, |
10 | }; | 10 | }; |
11 | use ide_assists::AssistResolveStrategy; | ||
11 | use ide_db::{ | 12 | use ide_db::{ |
12 | base_db::{AnchoredPathBuf, FileId}, | 13 | base_db::{AnchoredPathBuf, FileId}, |
13 | source_change::{FileSystemEdit, SourceChange}, | 14 | source_change::{FileSystemEdit, SourceChange}, |
@@ -35,11 +36,19 @@ pub(crate) trait DiagnosticWithFix: Diagnostic { | |||
35 | /// | 36 | /// |
36 | /// If `resolve` is false, the edit will be computed later, on demand, and | 37 | /// If `resolve` is false, the edit will be computed later, on demand, and |
37 | /// can be omitted. | 38 | /// can be omitted. |
38 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; | 39 | fn fix( |
40 | &self, | ||
41 | sema: &Semantics<RootDatabase>, | ||
42 | _resolve: &AssistResolveStrategy, | ||
43 | ) -> Option<Assist>; | ||
39 | } | 44 | } |
40 | 45 | ||
41 | impl DiagnosticWithFix for UnresolvedModule { | 46 | impl DiagnosticWithFix for UnresolvedModule { |
42 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 47 | fn fix( |
48 | &self, | ||
49 | sema: &Semantics<RootDatabase>, | ||
50 | _resolve: &AssistResolveStrategy, | ||
51 | ) -> Option<Assist> { | ||
43 | let root = sema.db.parse_or_expand(self.file)?; | 52 | let root = sema.db.parse_or_expand(self.file)?; |
44 | let unresolved_module = self.decl.to_node(&root); | 53 | let unresolved_module = self.decl.to_node(&root); |
45 | Some(fix( | 54 | Some(fix( |
@@ -59,7 +68,11 @@ impl DiagnosticWithFix for UnresolvedModule { | |||
59 | } | 68 | } |
60 | 69 | ||
61 | impl DiagnosticWithFix for NoSuchField { | 70 | impl DiagnosticWithFix for NoSuchField { |
62 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 71 | fn fix( |
72 | &self, | ||
73 | sema: &Semantics<RootDatabase>, | ||
74 | _resolve: &AssistResolveStrategy, | ||
75 | ) -> Option<Assist> { | ||
63 | let root = sema.db.parse_or_expand(self.file)?; | 76 | let root = sema.db.parse_or_expand(self.file)?; |
64 | missing_record_expr_field_fix( | 77 | missing_record_expr_field_fix( |
65 | &sema, | 78 | &sema, |
@@ -70,7 +83,11 @@ impl DiagnosticWithFix for NoSuchField { | |||
70 | } | 83 | } |
71 | 84 | ||
72 | impl DiagnosticWithFix for MissingFields { | 85 | impl DiagnosticWithFix for MissingFields { |
73 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 86 | fn fix( |
87 | &self, | ||
88 | sema: &Semantics<RootDatabase>, | ||
89 | _resolve: &AssistResolveStrategy, | ||
90 | ) -> Option<Assist> { | ||
74 | // Note that although we could add a diagnostics to | 91 | // Note that although we could add a diagnostics to |
75 | // fill the missing tuple field, e.g : | 92 | // fill the missing tuple field, e.g : |
76 | // `struct A(usize);` | 93 | // `struct A(usize);` |
@@ -106,7 +123,11 @@ impl DiagnosticWithFix for MissingFields { | |||
106 | } | 123 | } |
107 | 124 | ||
108 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | 125 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { |
109 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 126 | fn fix( |
127 | &self, | ||
128 | sema: &Semantics<RootDatabase>, | ||
129 | _resolve: &AssistResolveStrategy, | ||
130 | ) -> Option<Assist> { | ||
110 | let root = sema.db.parse_or_expand(self.file)?; | 131 | let root = sema.db.parse_or_expand(self.file)?; |
111 | let tail_expr = self.expr.to_node(&root); | 132 | let tail_expr = self.expr.to_node(&root); |
112 | let tail_expr_range = tail_expr.syntax().text_range(); | 133 | let tail_expr_range = tail_expr.syntax().text_range(); |
@@ -119,7 +140,11 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | |||
119 | } | 140 | } |
120 | 141 | ||
121 | impl DiagnosticWithFix for RemoveThisSemicolon { | 142 | impl DiagnosticWithFix for RemoveThisSemicolon { |
122 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 143 | fn fix( |
144 | &self, | ||
145 | sema: &Semantics<RootDatabase>, | ||
146 | _resolve: &AssistResolveStrategy, | ||
147 | ) -> Option<Assist> { | ||
123 | let root = sema.db.parse_or_expand(self.file)?; | 148 | let root = sema.db.parse_or_expand(self.file)?; |
124 | 149 | ||
125 | let semicolon = self | 150 | let semicolon = self |
@@ -139,7 +164,11 @@ impl DiagnosticWithFix for RemoveThisSemicolon { | |||
139 | } | 164 | } |
140 | 165 | ||
141 | impl DiagnosticWithFix for IncorrectCase { | 166 | impl DiagnosticWithFix for IncorrectCase { |
142 | fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> { | 167 | fn fix( |
168 | &self, | ||
169 | sema: &Semantics<RootDatabase>, | ||
170 | resolve: &AssistResolveStrategy, | ||
171 | ) -> Option<Assist> { | ||
143 | let root = sema.db.parse_or_expand(self.file)?; | 172 | let root = sema.db.parse_or_expand(self.file)?; |
144 | let name_node = self.ident.to_node(&root); | 173 | let name_node = self.ident.to_node(&root); |
145 | 174 | ||
@@ -149,7 +178,7 @@ impl DiagnosticWithFix for IncorrectCase { | |||
149 | 178 | ||
150 | let label = format!("Rename to {}", self.suggested_text); | 179 | let label = format!("Rename to {}", self.suggested_text); |
151 | let mut res = unresolved_fix("change_case", &label, frange.range); | 180 | let mut res = unresolved_fix("change_case", &label, frange.range); |
152 | if resolve { | 181 | if resolve.should_resolve(&res.id) { |
153 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); | 182 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); |
154 | res.source_change = Some(source_change.ok().unwrap_or_default()); | 183 | res.source_change = Some(source_change.ok().unwrap_or_default()); |
155 | } | 184 | } |
@@ -159,7 +188,11 @@ impl DiagnosticWithFix for IncorrectCase { | |||
159 | } | 188 | } |
160 | 189 | ||
161 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | 190 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { |
162 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 191 | fn fix( |
192 | &self, | ||
193 | sema: &Semantics<RootDatabase>, | ||
194 | _resolve: &AssistResolveStrategy, | ||
195 | ) -> Option<Assist> { | ||
163 | let root = sema.db.parse_or_expand(self.file)?; | 196 | let root = sema.db.parse_or_expand(self.file)?; |
164 | let next_expr = self.next_expr.to_node(&root); | 197 | let next_expr = self.next_expr.to_node(&root); |
165 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | 198 | 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 7d39f4fbe..93fd25dea 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide/src/diagnostics/unlinked_file.rs | |||
@@ -5,6 +5,7 @@ use hir::{ | |||
5 | diagnostics::{Diagnostic, DiagnosticCode}, | 5 | diagnostics::{Diagnostic, DiagnosticCode}, |
6 | InFile, | 6 | InFile, |
7 | }; | 7 | }; |
8 | use ide_assists::AssistResolveStrategy; | ||
8 | use ide_db::{ | 9 | use ide_db::{ |
9 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, | 10 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, |
10 | source_change::SourceChange, | 11 | source_change::SourceChange, |
@@ -50,7 +51,11 @@ impl Diagnostic for UnlinkedFile { | |||
50 | } | 51 | } |
51 | 52 | ||
52 | impl DiagnosticWithFix for UnlinkedFile { | 53 | impl DiagnosticWithFix for UnlinkedFile { |
53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 54 | fn fix( |
55 | &self, | ||
56 | sema: &hir::Semantics<RootDatabase>, | ||
57 | _resolve: &AssistResolveStrategy, | ||
58 | ) -> Option<Assist> { | ||
54 | // If there's an existing module that could add a `mod` item to include the unlinked file, | 59 | // If there's an existing module that could add a `mod` item to include the unlinked file, |
55 | // suggest that as a fix. | 60 | // suggest that as a fix. |
56 | 61 | ||
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 99e45633e..8e5b72044 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -87,7 +87,9 @@ pub use crate::{ | |||
87 | }, | 87 | }, |
88 | }; | 88 | }; |
89 | pub use hir::{Documentation, Semantics}; | 89 | pub use hir::{Documentation, Semantics}; |
90 | pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; | 90 | pub use ide_assists::{ |
91 | Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, | ||
92 | }; | ||
91 | pub use ide_completion::{ | 93 | pub use ide_completion::{ |
92 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, | 94 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, |
93 | InsertTextFormat, | 95 | InsertTextFormat, |
@@ -518,12 +520,13 @@ impl Analysis { | |||
518 | pub fn assists( | 520 | pub fn assists( |
519 | &self, | 521 | &self, |
520 | config: &AssistConfig, | 522 | config: &AssistConfig, |
521 | resolve: bool, | 523 | resolve: AssistResolveStrategy, |
522 | frange: FileRange, | 524 | frange: FileRange, |
523 | ) -> Cancelable<Vec<Assist>> { | 525 | ) -> Cancelable<Vec<Assist>> { |
524 | self.with_db(|db| { | 526 | self.with_db(|db| { |
527 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | ||
525 | let mut acc = Assist::get(db, config, resolve, frange); | 528 | let mut acc = Assist::get(db, config, resolve, frange); |
526 | ssr::add_ssr_assist(db, &mut acc, resolve, frange); | 529 | acc.extend(ssr_assists.into_iter()); |
527 | acc | 530 | acc |
528 | }) | 531 | }) |
529 | } | 532 | } |
@@ -532,10 +535,10 @@ impl Analysis { | |||
532 | pub fn diagnostics( | 535 | pub fn diagnostics( |
533 | &self, | 536 | &self, |
534 | config: &DiagnosticsConfig, | 537 | config: &DiagnosticsConfig, |
535 | resolve: bool, | 538 | resolve: AssistResolveStrategy, |
536 | file_id: FileId, | 539 | file_id: FileId, |
537 | ) -> Cancelable<Vec<Diagnostic>> { | 540 | ) -> Cancelable<Vec<Diagnostic>> { |
538 | self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id)) | 541 | self.with_db(|db| diagnostics::diagnostics(db, config, &resolve, file_id)) |
539 | } | 542 | } |
540 | 543 | ||
541 | /// Convenience function to return assists + quick fixes for diagnostics | 544 | /// Convenience function to return assists + quick fixes for diagnostics |
@@ -543,7 +546,7 @@ impl Analysis { | |||
543 | &self, | 546 | &self, |
544 | assist_config: &AssistConfig, | 547 | assist_config: &AssistConfig, |
545 | diagnostics_config: &DiagnosticsConfig, | 548 | diagnostics_config: &DiagnosticsConfig, |
546 | resolve: bool, | 549 | resolve: AssistResolveStrategy, |
547 | frange: FileRange, | 550 | frange: FileRange, |
548 | ) -> Cancelable<Vec<Assist>> { | 551 | ) -> Cancelable<Vec<Assist>> { |
549 | let include_fixes = match &assist_config.allowed { | 552 | let include_fixes = match &assist_config.allowed { |
@@ -552,17 +555,21 @@ impl Analysis { | |||
552 | }; | 555 | }; |
553 | 556 | ||
554 | self.with_db(|db| { | 557 | self.with_db(|db| { |
558 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | ||
559 | let diagnostic_assists = if include_fixes { | ||
560 | diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) | ||
561 | .into_iter() | ||
562 | .filter_map(|it| it.fix) | ||
563 | .filter(|it| it.target.intersect(frange.range).is_some()) | ||
564 | .collect() | ||
565 | } else { | ||
566 | Vec::new() | ||
567 | }; | ||
568 | |||
555 | let mut res = Assist::get(db, assist_config, resolve, frange); | 569 | let mut res = Assist::get(db, assist_config, resolve, frange); |
556 | ssr::add_ssr_assist(db, &mut res, resolve, frange); | 570 | res.extend(ssr_assists.into_iter()); |
557 | 571 | res.extend(diagnostic_assists.into_iter()); | |
558 | if include_fixes { | 572 | |
559 | res.extend( | ||
560 | diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id) | ||
561 | .into_iter() | ||
562 | .filter_map(|it| it.fix) | ||
563 | .filter(|it| it.target.intersect(frange.range).is_some()), | ||
564 | ); | ||
565 | } | ||
566 | res | 573 | res |
567 | }) | 574 | }) |
568 | } | 575 | } |
diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs index f3638d928..57ec80261 100644 --- a/crates/ide/src/ssr.rs +++ b/crates/ide/src/ssr.rs | |||
@@ -2,18 +2,23 @@ | |||
2 | //! assist in ide_assists because that would require the ide_assists crate | 2 | //! assist in ide_assists because that would require the ide_assists crate |
3 | //! depend on the ide_ssr crate. | 3 | //! depend on the ide_ssr crate. |
4 | 4 | ||
5 | use ide_assists::{Assist, AssistId, AssistKind, GroupLabel}; | 5 | use ide_assists::{Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel}; |
6 | use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; | 6 | use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; |
7 | 7 | ||
8 | pub(crate) fn add_ssr_assist( | 8 | pub(crate) fn ssr_assists( |
9 | db: &RootDatabase, | 9 | db: &RootDatabase, |
10 | base: &mut Vec<Assist>, | 10 | resolve: &AssistResolveStrategy, |
11 | resolve: bool, | ||
12 | frange: FileRange, | 11 | frange: FileRange, |
13 | ) -> Option<()> { | 12 | ) -> Vec<Assist> { |
14 | let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?; | 13 | let mut ssr_assists = Vec::with_capacity(2); |
15 | 14 | ||
16 | let (source_change_for_file, source_change_for_workspace) = if resolve { | 15 | let (match_finder, comment_range) = match ide_ssr::ssr_from_comment(db, frange) { |
16 | Some(ssr_data) => ssr_data, | ||
17 | None => return ssr_assists, | ||
18 | }; | ||
19 | let id = AssistId("ssr", AssistKind::RefactorRewrite); | ||
20 | |||
21 | let (source_change_for_file, source_change_for_workspace) = if resolve.should_resolve(&id) { | ||
17 | let edits = match_finder.edits(); | 22 | let edits = match_finder.edits(); |
18 | 23 | ||
19 | let source_change_for_file = { | 24 | let source_change_for_file = { |
@@ -35,16 +40,17 @@ pub(crate) fn add_ssr_assist( | |||
35 | 40 | ||
36 | for (label, source_change) in assists.into_iter() { | 41 | for (label, source_change) in assists.into_iter() { |
37 | let assist = Assist { | 42 | let assist = Assist { |
38 | id: AssistId("ssr", AssistKind::RefactorRewrite), | 43 | id, |
39 | label: Label::new(label), | 44 | label: Label::new(label), |
40 | group: Some(GroupLabel("Apply SSR".into())), | 45 | group: Some(GroupLabel("Apply SSR".into())), |
41 | target: comment_range, | 46 | target: comment_range, |
42 | source_change, | 47 | source_change, |
43 | }; | 48 | }; |
44 | 49 | ||
45 | base.push(assist); | 50 | ssr_assists.push(assist); |
46 | } | 51 | } |
47 | Some(()) | 52 | |
53 | ssr_assists | ||
48 | } | 54 | } |
49 | 55 | ||
50 | #[cfg(test)] | 56 | #[cfg(test)] |
@@ -52,7 +58,7 @@ mod tests { | |||
52 | use std::sync::Arc; | 58 | use std::sync::Arc; |
53 | 59 | ||
54 | use expect_test::expect; | 60 | use expect_test::expect; |
55 | use ide_assists::Assist; | 61 | use ide_assists::{Assist, AssistResolveStrategy}; |
56 | use ide_db::{ | 62 | use ide_db::{ |
57 | base_db::{fixture::WithFixture, salsa::Durability, FileRange}, | 63 | base_db::{fixture::WithFixture, salsa::Durability, FileRange}, |
58 | symbol_index::SymbolsDatabase, | 64 | symbol_index::SymbolsDatabase, |
@@ -60,24 +66,14 @@ mod tests { | |||
60 | }; | 66 | }; |
61 | use rustc_hash::FxHashSet; | 67 | use rustc_hash::FxHashSet; |
62 | 68 | ||
63 | use super::add_ssr_assist; | 69 | use super::ssr_assists; |
64 | 70 | ||
65 | fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> { | 71 | fn get_assists(ra_fixture: &str, resolve: AssistResolveStrategy) -> Vec<Assist> { |
66 | let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); | 72 | let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); |
67 | let mut local_roots = FxHashSet::default(); | 73 | let mut local_roots = FxHashSet::default(); |
68 | local_roots.insert(ide_db::base_db::fixture::WORKSPACE); | 74 | local_roots.insert(ide_db::base_db::fixture::WORKSPACE); |
69 | db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); | 75 | db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); |
70 | 76 | ssr_assists(&db, &resolve, FileRange { file_id, range: range_or_offset.into() }) | |
71 | let mut assists = vec![]; | ||
72 | |||
73 | add_ssr_assist( | ||
74 | &db, | ||
75 | &mut assists, | ||
76 | resolve, | ||
77 | FileRange { file_id, range: range_or_offset.into() }, | ||
78 | ); | ||
79 | |||
80 | assists | ||
81 | } | 77 | } |
82 | 78 | ||
83 | #[test] | 79 | #[test] |
@@ -88,16 +84,13 @@ mod tests { | |||
88 | // This is foo $0 | 84 | // This is foo $0 |
89 | fn foo() {} | 85 | fn foo() {} |
90 | "#; | 86 | "#; |
91 | let resolve = true; | 87 | let assists = get_assists(ra_fixture, AssistResolveStrategy::All); |
92 | |||
93 | let assists = get_assists(ra_fixture, resolve); | ||
94 | 88 | ||
95 | assert_eq!(0, assists.len()); | 89 | assert_eq!(0, assists.len()); |
96 | } | 90 | } |
97 | 91 | ||
98 | #[test] | 92 | #[test] |
99 | fn resolve_edits_true() { | 93 | fn resolve_edits_true() { |
100 | let resolve = true; | ||
101 | let assists = get_assists( | 94 | let assists = get_assists( |
102 | r#" | 95 | r#" |
103 | //- /lib.rs | 96 | //- /lib.rs |
@@ -109,7 +102,7 @@ mod tests { | |||
109 | //- /bar.rs | 102 | //- /bar.rs |
110 | fn bar() { 2 } | 103 | fn bar() { 2 } |
111 | "#, | 104 | "#, |
112 | resolve, | 105 | AssistResolveStrategy::All, |
113 | ); | 106 | ); |
114 | 107 | ||
115 | assert_eq!(2, assists.len()); | 108 | assert_eq!(2, assists.len()); |
@@ -200,7 +193,6 @@ mod tests { | |||
200 | 193 | ||
201 | #[test] | 194 | #[test] |
202 | fn resolve_edits_false() { | 195 | fn resolve_edits_false() { |
203 | let resolve = false; | ||
204 | let assists = get_assists( | 196 | let assists = get_assists( |
205 | r#" | 197 | r#" |
206 | //- /lib.rs | 198 | //- /lib.rs |
@@ -212,7 +204,7 @@ mod tests { | |||
212 | //- /bar.rs | 204 | //- /bar.rs |
213 | fn bar() { 2 } | 205 | fn bar() { 2 } |
214 | "#, | 206 | "#, |
215 | resolve, | 207 | AssistResolveStrategy::None, |
216 | ); | 208 | ); |
217 | 209 | ||
218 | assert_eq!(2, assists.len()); | 210 | assert_eq!(2, assists.len()); |
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs index 8714e4978..112939948 100644 --- a/crates/ide_assists/src/assist_context.rs +++ b/crates/ide_assists/src/assist_context.rs | |||
@@ -19,7 +19,9 @@ use syntax::{ | |||
19 | }; | 19 | }; |
20 | use text_edit::{TextEdit, TextEditBuilder}; | 20 | use text_edit::{TextEdit, TextEditBuilder}; |
21 | 21 | ||
22 | use crate::{assist_config::AssistConfig, Assist, AssistId, AssistKind, GroupLabel}; | 22 | use crate::{ |
23 | assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, | ||
24 | }; | ||
23 | 25 | ||
24 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 26 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
25 | /// | 27 | /// |
@@ -105,14 +107,14 @@ impl<'a> AssistContext<'a> { | |||
105 | } | 107 | } |
106 | 108 | ||
107 | pub(crate) struct Assists { | 109 | pub(crate) struct Assists { |
108 | resolve: bool, | ||
109 | file: FileId, | 110 | file: FileId, |
111 | resolve: AssistResolveStrategy, | ||
110 | buf: Vec<Assist>, | 112 | buf: Vec<Assist>, |
111 | allowed: Option<Vec<AssistKind>>, | 113 | allowed: Option<Vec<AssistKind>>, |
112 | } | 114 | } |
113 | 115 | ||
114 | impl Assists { | 116 | impl Assists { |
115 | pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists { | 117 | pub(crate) fn new(ctx: &AssistContext, resolve: AssistResolveStrategy) -> Assists { |
116 | Assists { | 118 | Assists { |
117 | resolve, | 119 | resolve, |
118 | file: ctx.frange.file_id, | 120 | file: ctx.frange.file_id, |
@@ -158,7 +160,7 @@ impl Assists { | |||
158 | } | 160 | } |
159 | 161 | ||
160 | fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | 162 | fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { |
161 | let source_change = if self.resolve { | 163 | let source_change = if self.resolve.should_resolve(&assist.id) { |
162 | let mut builder = AssistBuilder::new(self.file); | 164 | let mut builder = AssistBuilder::new(self.file); |
163 | f(&mut builder); | 165 | f(&mut builder); |
164 | Some(builder.finish()) | 166 | Some(builder.finish()) |
@@ -185,7 +187,29 @@ pub(crate) struct AssistBuilder { | |||
185 | source_change: SourceChange, | 187 | source_change: SourceChange, |
186 | 188 | ||
187 | /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin. | 189 | /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin. |
188 | mutated_tree: Option<(SyntaxNode, SyntaxNode)>, | 190 | mutated_tree: Option<TreeMutator>, |
191 | } | ||
192 | |||
193 | pub(crate) struct TreeMutator { | ||
194 | immutable: SyntaxNode, | ||
195 | mutable_clone: SyntaxNode, | ||
196 | } | ||
197 | |||
198 | impl TreeMutator { | ||
199 | pub(crate) fn new(immutable: &SyntaxNode) -> TreeMutator { | ||
200 | let immutable = immutable.ancestors().last().unwrap(); | ||
201 | let mutable_clone = immutable.clone_for_update(); | ||
202 | TreeMutator { immutable, mutable_clone } | ||
203 | } | ||
204 | |||
205 | pub(crate) fn make_mut<N: AstNode>(&self, node: &N) -> N { | ||
206 | N::cast(self.make_syntax_mut(node.syntax())).unwrap() | ||
207 | } | ||
208 | |||
209 | pub(crate) fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode { | ||
210 | let ptr = SyntaxNodePtr::new(node); | ||
211 | ptr.to_node(&self.mutable_clone) | ||
212 | } | ||
189 | } | 213 | } |
190 | 214 | ||
191 | impl AssistBuilder { | 215 | impl AssistBuilder { |
@@ -204,8 +228,8 @@ impl AssistBuilder { | |||
204 | } | 228 | } |
205 | 229 | ||
206 | fn commit(&mut self) { | 230 | fn commit(&mut self) { |
207 | if let Some((old, new)) = self.mutated_tree.take() { | 231 | if let Some(tm) = self.mutated_tree.take() { |
208 | algo::diff(&old, &new).into_text_edit(&mut self.edit) | 232 | algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit) |
209 | } | 233 | } |
210 | 234 | ||
211 | let edit = mem::take(&mut self.edit).finish(); | 235 | let edit = mem::take(&mut self.edit).finish(); |
@@ -228,16 +252,7 @@ impl AssistBuilder { | |||
228 | /// phase, and then get their mutable couterparts using `make_mut` in the | 252 | /// phase, and then get their mutable couterparts using `make_mut` in the |
229 | /// mutable state. | 253 | /// mutable state. |
230 | pub(crate) fn make_mut(&mut self, node: SyntaxNode) -> SyntaxNode { | 254 | pub(crate) fn make_mut(&mut self, node: SyntaxNode) -> SyntaxNode { |
231 | let root = &self | 255 | self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node) |
232 | .mutated_tree | ||
233 | .get_or_insert_with(|| { | ||
234 | let immutable = node.ancestors().last().unwrap(); | ||
235 | let mutable = immutable.clone_for_update(); | ||
236 | (immutable, mutable) | ||
237 | }) | ||
238 | .1; | ||
239 | let ptr = SyntaxNodePtr::new(&&node); | ||
240 | ptr.to_node(root) | ||
241 | } | 256 | } |
242 | 257 | ||
243 | /// Remove specified `range` of text. | 258 | /// Remove specified `range` of text. |
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index b30652a9d..93b28370c 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs | |||
@@ -16,12 +16,13 @@ use syntax::{ | |||
16 | edit::{AstNodeEdit, IndentLevel}, | 16 | edit::{AstNodeEdit, IndentLevel}, |
17 | AstNode, | 17 | AstNode, |
18 | }, | 18 | }, |
19 | ted, | ||
19 | SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR}, | 20 | SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR}, |
20 | SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T, | 21 | SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T, |
21 | }; | 22 | }; |
22 | 23 | ||
23 | use crate::{ | 24 | use crate::{ |
24 | assist_context::{AssistContext, Assists}, | 25 | assist_context::{AssistContext, Assists, TreeMutator}, |
25 | AssistId, | 26 | AssistId, |
26 | }; | 27 | }; |
27 | 28 | ||
@@ -1366,7 +1367,10 @@ fn rewrite_body_segment( | |||
1366 | 1367 | ||
1367 | /// change all usages to account for added `&`/`&mut` for some params | 1368 | /// change all usages to account for added `&`/`&mut` for some params |
1368 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { | 1369 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { |
1369 | let mut rewriter = SyntaxRewriter::default(); | 1370 | let mut usages_for_param: Vec<(&Param, Vec<ast::Expr>)> = Vec::new(); |
1371 | |||
1372 | let tm = TreeMutator::new(syntax); | ||
1373 | |||
1370 | for param in params { | 1374 | for param in params { |
1371 | if !param.kind().is_ref() { | 1375 | if !param.kind().is_ref() { |
1372 | continue; | 1376 | continue; |
@@ -1376,30 +1380,39 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) | |||
1376 | let usages = usages | 1380 | let usages = usages |
1377 | .iter() | 1381 | .iter() |
1378 | .filter(|reference| syntax.text_range().contains_range(reference.range)) | 1382 | .filter(|reference| syntax.text_range().contains_range(reference.range)) |
1379 | .filter_map(|reference| path_element_of_reference(syntax, reference)); | 1383 | .filter_map(|reference| path_element_of_reference(syntax, reference)) |
1380 | for path in usages { | 1384 | .map(|expr| tm.make_mut(&expr)); |
1381 | match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) { | 1385 | |
1386 | usages_for_param.push((param, usages.collect())); | ||
1387 | } | ||
1388 | |||
1389 | let res = tm.make_syntax_mut(syntax); | ||
1390 | |||
1391 | for (param, usages) in usages_for_param { | ||
1392 | for usage in usages { | ||
1393 | match usage.syntax().ancestors().skip(1).find_map(ast::Expr::cast) { | ||
1382 | Some(ast::Expr::MethodCallExpr(_)) | Some(ast::Expr::FieldExpr(_)) => { | 1394 | Some(ast::Expr::MethodCallExpr(_)) | Some(ast::Expr::FieldExpr(_)) => { |
1383 | // do nothing | 1395 | // do nothing |
1384 | } | 1396 | } |
1385 | Some(ast::Expr::RefExpr(node)) | 1397 | Some(ast::Expr::RefExpr(node)) |
1386 | if param.kind() == ParamKind::MutRef && node.mut_token().is_some() => | 1398 | if param.kind() == ParamKind::MutRef && node.mut_token().is_some() => |
1387 | { | 1399 | { |
1388 | rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); | 1400 | ted::replace(node.syntax(), node.expr().unwrap().syntax()); |
1389 | } | 1401 | } |
1390 | Some(ast::Expr::RefExpr(node)) | 1402 | Some(ast::Expr::RefExpr(node)) |
1391 | if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() => | 1403 | if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() => |
1392 | { | 1404 | { |
1393 | rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); | 1405 | ted::replace(node.syntax(), node.expr().unwrap().syntax()); |
1394 | } | 1406 | } |
1395 | Some(_) | None => { | 1407 | Some(_) | None => { |
1396 | rewriter.replace_ast(&path, &make::expr_prefix(T![*], path.clone())); | 1408 | let p = &make::expr_prefix(T![*], usage.clone()).clone_for_update(); |
1409 | ted::replace(usage.syntax(), p.syntax()) | ||
1397 | } | 1410 | } |
1398 | }; | 1411 | } |
1399 | } | 1412 | } |
1400 | } | 1413 | } |
1401 | 1414 | ||
1402 | rewriter.rewrite(syntax) | 1415 | res |
1403 | } | 1416 | } |
1404 | 1417 | ||
1405 | fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode { | 1418 | fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode { |
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs index 72d889248..54a9a468e 100644 --- a/crates/ide_assists/src/handlers/reorder_impl.rs +++ b/crates/ide_assists/src/handlers/reorder_impl.rs | |||
@@ -79,9 +79,12 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
79 | "Sort methods", | 79 | "Sort methods", |
80 | target, | 80 | target, |
81 | |builder| { | 81 | |builder| { |
82 | methods.into_iter().zip(sorted).for_each(|(old, new)| { | 82 | let methods = |
83 | ted::replace(builder.make_ast_mut(old).syntax(), new.clone_for_update().syntax()) | 83 | methods.into_iter().map(|fn_| builder.make_ast_mut(fn_)).collect::<Vec<_>>(); |
84 | }); | 84 | methods |
85 | .into_iter() | ||
86 | .zip(sorted) | ||
87 | .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax())); | ||
85 | }, | 88 | }, |
86 | ) | 89 | ) |
87 | } | 90 | } |
@@ -160,7 +163,7 @@ $0impl Bar for Foo {} | |||
160 | } | 163 | } |
161 | 164 | ||
162 | #[test] | 165 | #[test] |
163 | fn reorder_impl_trait_methods() { | 166 | fn reorder_impl_trait_functions() { |
164 | check_assist( | 167 | check_assist( |
165 | reorder_impl, | 168 | reorder_impl, |
166 | r#" | 169 | r#" |
@@ -197,4 +200,33 @@ impl Bar for Foo { | |||
197 | "#, | 200 | "#, |
198 | ) | 201 | ) |
199 | } | 202 | } |
203 | |||
204 | #[test] | ||
205 | fn reorder_impl_trait_methods_uneven_ident_lengths() { | ||
206 | check_assist( | ||
207 | reorder_impl, | ||
208 | r#" | ||
209 | trait Bar { | ||
210 | fn foo(&mut self) {} | ||
211 | fn fooo(&mut self) {} | ||
212 | } | ||
213 | |||
214 | struct Foo; | ||
215 | impl Bar for Foo { | ||
216 | fn fooo(&mut self) {} | ||
217 | fn foo(&mut self) {$0} | ||
218 | }"#, | ||
219 | r#" | ||
220 | trait Bar { | ||
221 | fn foo(&mut self) {} | ||
222 | fn fooo(&mut self) {} | ||
223 | } | ||
224 | |||
225 | struct Foo; | ||
226 | impl Bar for Foo { | ||
227 | fn foo(&mut self) {} | ||
228 | fn fooo(&mut self) {} | ||
229 | }"#, | ||
230 | ) | ||
231 | } | ||
200 | } | 232 | } |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 88ae5c9a9..2e0c58504 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -17,6 +17,8 @@ mod tests; | |||
17 | pub mod utils; | 17 | pub mod utils; |
18 | pub mod ast_transform; | 18 | pub mod ast_transform; |
19 | 19 | ||
20 | use std::str::FromStr; | ||
21 | |||
20 | use hir::Semantics; | 22 | use hir::Semantics; |
21 | use ide_db::base_db::FileRange; | 23 | use ide_db::base_db::FileRange; |
22 | use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; | 24 | use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; |
@@ -56,6 +58,35 @@ impl AssistKind { | |||
56 | _ => return false, | 58 | _ => return false, |
57 | } | 59 | } |
58 | } | 60 | } |
61 | |||
62 | pub fn name(&self) -> &str { | ||
63 | match self { | ||
64 | AssistKind::None => "None", | ||
65 | AssistKind::QuickFix => "QuickFix", | ||
66 | AssistKind::Generate => "Generate", | ||
67 | AssistKind::Refactor => "Refactor", | ||
68 | AssistKind::RefactorExtract => "RefactorExtract", | ||
69 | AssistKind::RefactorInline => "RefactorInline", | ||
70 | AssistKind::RefactorRewrite => "RefactorRewrite", | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | impl FromStr for AssistKind { | ||
76 | type Err = String; | ||
77 | |||
78 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
79 | match s { | ||
80 | "None" => Ok(AssistKind::None), | ||
81 | "QuickFix" => Ok(AssistKind::QuickFix), | ||
82 | "Generate" => Ok(AssistKind::Generate), | ||
83 | "Refactor" => Ok(AssistKind::Refactor), | ||
84 | "RefactorExtract" => Ok(AssistKind::RefactorExtract), | ||
85 | "RefactorInline" => Ok(AssistKind::RefactorInline), | ||
86 | "RefactorRewrite" => Ok(AssistKind::RefactorRewrite), | ||
87 | unknown => Err(format!("Unknown AssistKind: '{}'", unknown)), | ||
88 | } | ||
89 | } | ||
59 | } | 90 | } |
60 | 91 | ||
61 | /// Unique identifier of the assist, should not be shown to the user | 92 | /// Unique identifier of the assist, should not be shown to the user |
@@ -63,6 +94,41 @@ impl AssistKind { | |||
63 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 94 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
64 | pub struct AssistId(pub &'static str, pub AssistKind); | 95 | pub struct AssistId(pub &'static str, pub AssistKind); |
65 | 96 | ||
97 | /// A way to control how many asssist to resolve during the assist resolution. | ||
98 | /// When an assist is resolved, its edits are calculated that might be costly to always do by default. | ||
99 | #[derive(Debug)] | ||
100 | pub enum AssistResolveStrategy { | ||
101 | /// No assists should be resolved. | ||
102 | None, | ||
103 | /// All assists should be resolved. | ||
104 | All, | ||
105 | /// Only a certain assist should be resolved. | ||
106 | Single(SingleResolve), | ||
107 | } | ||
108 | |||
109 | /// Hold the [`AssistId`] data of a certain assist to resolve. | ||
110 | /// The original id object cannot be used due to a `'static` lifetime | ||
111 | /// and the requirement to construct this struct dynamically during the resolve handling. | ||
112 | #[derive(Debug)] | ||
113 | pub struct SingleResolve { | ||
114 | /// The id of the assist. | ||
115 | pub assist_id: String, | ||
116 | // The kind of the assist. | ||
117 | pub assist_kind: AssistKind, | ||
118 | } | ||
119 | |||
120 | impl AssistResolveStrategy { | ||
121 | pub fn should_resolve(&self, id: &AssistId) -> bool { | ||
122 | match self { | ||
123 | AssistResolveStrategy::None => false, | ||
124 | AssistResolveStrategy::All => true, | ||
125 | AssistResolveStrategy::Single(single_resolve) => { | ||
126 | single_resolve.assist_id == id.0 && single_resolve.assist_kind == id.1 | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
66 | #[derive(Clone, Debug)] | 132 | #[derive(Clone, Debug)] |
67 | pub struct GroupLabel(pub String); | 133 | pub struct GroupLabel(pub String); |
68 | 134 | ||
@@ -91,7 +157,7 @@ impl Assist { | |||
91 | pub fn get( | 157 | pub fn get( |
92 | db: &RootDatabase, | 158 | db: &RootDatabase, |
93 | config: &AssistConfig, | 159 | config: &AssistConfig, |
94 | resolve: bool, | 160 | resolve: AssistResolveStrategy, |
95 | range: FileRange, | 161 | range: FileRange, |
96 | ) -> Vec<Assist> { | 162 | ) -> Vec<Assist> { |
97 | let sema = Semantics::new(db); | 163 | let sema = Semantics::new(db); |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index 6f4f97361..9c2847998 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -12,7 +12,10 @@ use stdx::{format_to, trim_indent}; | |||
12 | use syntax::TextRange; | 12 | use syntax::TextRange; |
13 | use test_utils::{assert_eq_text, extract_offset}; | 13 | use test_utils::{assert_eq_text, extract_offset}; |
14 | 14 | ||
15 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; | 15 | use crate::{ |
16 | handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, AssistResolveStrategy, | ||
17 | Assists, SingleResolve, | ||
18 | }; | ||
16 | 19 | ||
17 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { | 20 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { |
18 | snippet_cap: SnippetCap::new(true), | 21 | snippet_cap: SnippetCap::new(true), |
@@ -65,14 +68,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
65 | let before = db.file_text(file_id).to_string(); | 68 | let before = db.file_text(file_id).to_string(); |
66 | let frange = FileRange { file_id, range: selection.into() }; | 69 | let frange = FileRange { file_id, range: selection.into() }; |
67 | 70 | ||
68 | let assist = Assist::get(&db, &TEST_CONFIG, true, frange) | 71 | let assist = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange) |
69 | .into_iter() | 72 | .into_iter() |
70 | .find(|assist| assist.id.0 == assist_id) | 73 | .find(|assist| assist.id.0 == assist_id) |
71 | .unwrap_or_else(|| { | 74 | .unwrap_or_else(|| { |
72 | panic!( | 75 | panic!( |
73 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 76 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
74 | assist_id, | 77 | assist_id, |
75 | Assist::get(&db, &TEST_CONFIG, false, frange) | 78 | Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange) |
76 | .into_iter() | 79 | .into_iter() |
77 | .map(|assist| assist.id.0) | 80 | .map(|assist| assist.id.0) |
78 | .collect::<Vec<_>>() | 81 | .collect::<Vec<_>>() |
@@ -108,7 +111,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: | |||
108 | let sema = Semantics::new(&db); | 111 | let sema = Semantics::new(&db); |
109 | let config = TEST_CONFIG; | 112 | let config = TEST_CONFIG; |
110 | let ctx = AssistContext::new(sema, &config, frange); | 113 | let ctx = AssistContext::new(sema, &config, frange); |
111 | let mut acc = Assists::new(&ctx, true); | 114 | let mut acc = Assists::new(&ctx, AssistResolveStrategy::All); |
112 | handler(&mut acc, &ctx); | 115 | handler(&mut acc, &ctx); |
113 | let mut res = acc.finish(); | 116 | let mut res = acc.finish(); |
114 | 117 | ||
@@ -186,7 +189,7 @@ fn assist_order_field_struct() { | |||
186 | let (before_cursor_pos, before) = extract_offset(before); | 189 | let (before_cursor_pos, before) = extract_offset(before); |
187 | let (db, file_id) = with_single_file(&before); | 190 | let (db, file_id) = with_single_file(&before); |
188 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 191 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
189 | let assists = Assist::get(&db, &TEST_CONFIG, false, frange); | 192 | let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); |
190 | let mut assists = assists.iter(); | 193 | let mut assists = assists.iter(); |
191 | 194 | ||
192 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 195 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
@@ -211,7 +214,7 @@ pub fn test_some_range(a: int) -> bool { | |||
211 | "#, | 214 | "#, |
212 | ); | 215 | ); |
213 | 216 | ||
214 | let assists = Assist::get(&db, &TEST_CONFIG, false, frange); | 217 | let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); |
215 | let expected = labels(&assists); | 218 | let expected = labels(&assists); |
216 | 219 | ||
217 | expect![[r#" | 220 | expect![[r#" |
@@ -240,7 +243,7 @@ pub fn test_some_range(a: int) -> bool { | |||
240 | let mut cfg = TEST_CONFIG; | 243 | let mut cfg = TEST_CONFIG; |
241 | cfg.allowed = Some(vec![AssistKind::Refactor]); | 244 | cfg.allowed = Some(vec![AssistKind::Refactor]); |
242 | 245 | ||
243 | let assists = Assist::get(&db, &cfg, false, frange); | 246 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); |
244 | let expected = labels(&assists); | 247 | let expected = labels(&assists); |
245 | 248 | ||
246 | expect![[r#" | 249 | expect![[r#" |
@@ -255,7 +258,7 @@ pub fn test_some_range(a: int) -> bool { | |||
255 | { | 258 | { |
256 | let mut cfg = TEST_CONFIG; | 259 | let mut cfg = TEST_CONFIG; |
257 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | 260 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); |
258 | let assists = Assist::get(&db, &cfg, false, frange); | 261 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); |
259 | let expected = labels(&assists); | 262 | let expected = labels(&assists); |
260 | 263 | ||
261 | expect![[r#" | 264 | expect![[r#" |
@@ -268,9 +271,250 @@ pub fn test_some_range(a: int) -> bool { | |||
268 | { | 271 | { |
269 | let mut cfg = TEST_CONFIG; | 272 | let mut cfg = TEST_CONFIG; |
270 | cfg.allowed = Some(vec![AssistKind::QuickFix]); | 273 | cfg.allowed = Some(vec![AssistKind::QuickFix]); |
271 | let assists = Assist::get(&db, &cfg, false, frange); | 274 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); |
272 | let expected = labels(&assists); | 275 | let expected = labels(&assists); |
273 | 276 | ||
274 | expect![[r#""#]].assert_eq(&expected); | 277 | expect![[r#""#]].assert_eq(&expected); |
275 | } | 278 | } |
276 | } | 279 | } |
280 | |||
281 | #[test] | ||
282 | fn various_resolve_strategies() { | ||
283 | let (db, frange) = RootDatabase::with_range( | ||
284 | r#" | ||
285 | pub fn test_some_range(a: int) -> bool { | ||
286 | if let 2..6 = $05$0 { | ||
287 | true | ||
288 | } else { | ||
289 | false | ||
290 | } | ||
291 | } | ||
292 | "#, | ||
293 | ); | ||
294 | |||
295 | let mut cfg = TEST_CONFIG; | ||
296 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | ||
297 | |||
298 | { | ||
299 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); | ||
300 | assert_eq!(2, assists.len()); | ||
301 | let mut assists = assists.into_iter(); | ||
302 | |||
303 | let extract_into_variable_assist = assists.next().unwrap(); | ||
304 | expect![[r#" | ||
305 | Assist { | ||
306 | id: AssistId( | ||
307 | "extract_variable", | ||
308 | RefactorExtract, | ||
309 | ), | ||
310 | label: "Extract into variable", | ||
311 | group: None, | ||
312 | target: 59..60, | ||
313 | source_change: None, | ||
314 | } | ||
315 | "#]] | ||
316 | .assert_debug_eq(&extract_into_variable_assist); | ||
317 | |||
318 | let extract_into_function_assist = assists.next().unwrap(); | ||
319 | expect![[r#" | ||
320 | Assist { | ||
321 | id: AssistId( | ||
322 | "extract_function", | ||
323 | RefactorExtract, | ||
324 | ), | ||
325 | label: "Extract into function", | ||
326 | group: None, | ||
327 | target: 59..60, | ||
328 | source_change: None, | ||
329 | } | ||
330 | "#]] | ||
331 | .assert_debug_eq(&extract_into_function_assist); | ||
332 | } | ||
333 | |||
334 | { | ||
335 | let assists = Assist::get( | ||
336 | &db, | ||
337 | &cfg, | ||
338 | AssistResolveStrategy::Single(SingleResolve { | ||
339 | assist_id: "SOMETHING_MISMATCHING".to_string(), | ||
340 | assist_kind: AssistKind::RefactorExtract, | ||
341 | }), | ||
342 | frange, | ||
343 | ); | ||
344 | assert_eq!(2, assists.len()); | ||
345 | let mut assists = assists.into_iter(); | ||
346 | |||
347 | let extract_into_variable_assist = assists.next().unwrap(); | ||
348 | expect![[r#" | ||
349 | Assist { | ||
350 | id: AssistId( | ||
351 | "extract_variable", | ||
352 | RefactorExtract, | ||
353 | ), | ||
354 | label: "Extract into variable", | ||
355 | group: None, | ||
356 | target: 59..60, | ||
357 | source_change: None, | ||
358 | } | ||
359 | "#]] | ||
360 | .assert_debug_eq(&extract_into_variable_assist); | ||
361 | |||
362 | let extract_into_function_assist = assists.next().unwrap(); | ||
363 | expect![[r#" | ||
364 | Assist { | ||
365 | id: AssistId( | ||
366 | "extract_function", | ||
367 | RefactorExtract, | ||
368 | ), | ||
369 | label: "Extract into function", | ||
370 | group: None, | ||
371 | target: 59..60, | ||
372 | source_change: None, | ||
373 | } | ||
374 | "#]] | ||
375 | .assert_debug_eq(&extract_into_function_assist); | ||
376 | } | ||
377 | |||
378 | { | ||
379 | let assists = Assist::get( | ||
380 | &db, | ||
381 | &cfg, | ||
382 | AssistResolveStrategy::Single(SingleResolve { | ||
383 | assist_id: "extract_variable".to_string(), | ||
384 | assist_kind: AssistKind::RefactorExtract, | ||
385 | }), | ||
386 | frange, | ||
387 | ); | ||
388 | assert_eq!(2, assists.len()); | ||
389 | let mut assists = assists.into_iter(); | ||
390 | |||
391 | let extract_into_variable_assist = assists.next().unwrap(); | ||
392 | expect![[r#" | ||
393 | Assist { | ||
394 | id: AssistId( | ||
395 | "extract_variable", | ||
396 | RefactorExtract, | ||
397 | ), | ||
398 | label: "Extract into variable", | ||
399 | group: None, | ||
400 | target: 59..60, | ||
401 | source_change: Some( | ||
402 | SourceChange { | ||
403 | source_file_edits: { | ||
404 | FileId( | ||
405 | 0, | ||
406 | ): TextEdit { | ||
407 | indels: [ | ||
408 | Indel { | ||
409 | insert: "let $0var_name = 5;\n ", | ||
410 | delete: 45..45, | ||
411 | }, | ||
412 | Indel { | ||
413 | insert: "var_name", | ||
414 | delete: 59..60, | ||
415 | }, | ||
416 | ], | ||
417 | }, | ||
418 | }, | ||
419 | file_system_edits: [], | ||
420 | is_snippet: true, | ||
421 | }, | ||
422 | ), | ||
423 | } | ||
424 | "#]] | ||
425 | .assert_debug_eq(&extract_into_variable_assist); | ||
426 | |||
427 | let extract_into_function_assist = assists.next().unwrap(); | ||
428 | expect![[r#" | ||
429 | Assist { | ||
430 | id: AssistId( | ||
431 | "extract_function", | ||
432 | RefactorExtract, | ||
433 | ), | ||
434 | label: "Extract into function", | ||
435 | group: None, | ||
436 | target: 59..60, | ||
437 | source_change: None, | ||
438 | } | ||
439 | "#]] | ||
440 | .assert_debug_eq(&extract_into_function_assist); | ||
441 | } | ||
442 | |||
443 | { | ||
444 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::All, frange); | ||
445 | assert_eq!(2, assists.len()); | ||
446 | let mut assists = assists.into_iter(); | ||
447 | |||
448 | let extract_into_variable_assist = assists.next().unwrap(); | ||
449 | expect![[r#" | ||
450 | Assist { | ||
451 | id: AssistId( | ||
452 | "extract_variable", | ||
453 | RefactorExtract, | ||
454 | ), | ||
455 | label: "Extract into variable", | ||
456 | group: None, | ||
457 | target: 59..60, | ||
458 | source_change: Some( | ||
459 | SourceChange { | ||
460 | source_file_edits: { | ||
461 | FileId( | ||
462 | 0, | ||
463 | ): TextEdit { | ||
464 | indels: [ | ||
465 | Indel { | ||
466 | insert: "let $0var_name = 5;\n ", | ||
467 | delete: 45..45, | ||
468 | }, | ||
469 | Indel { | ||
470 | insert: "var_name", | ||
471 | delete: 59..60, | ||
472 | }, | ||
473 | ], | ||
474 | }, | ||
475 | }, | ||
476 | file_system_edits: [], | ||
477 | is_snippet: true, | ||
478 | }, | ||
479 | ), | ||
480 | } | ||
481 | "#]] | ||
482 | .assert_debug_eq(&extract_into_variable_assist); | ||
483 | |||
484 | let extract_into_function_assist = assists.next().unwrap(); | ||
485 | expect![[r#" | ||
486 | Assist { | ||
487 | id: AssistId( | ||
488 | "extract_function", | ||
489 | RefactorExtract, | ||
490 | ), | ||
491 | label: "Extract into function", | ||
492 | group: None, | ||
493 | target: 59..60, | ||
494 | source_change: Some( | ||
495 | SourceChange { | ||
496 | source_file_edits: { | ||
497 | FileId( | ||
498 | 0, | ||
499 | ): TextEdit { | ||
500 | indels: [ | ||
501 | Indel { | ||
502 | insert: "fun_name()", | ||
503 | delete: 59..60, | ||
504 | }, | ||
505 | Indel { | ||
506 | insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}", | ||
507 | delete: 110..110, | ||
508 | }, | ||
509 | ], | ||
510 | }, | ||
511 | }, | ||
512 | file_system_edits: [], | ||
513 | is_snippet: true, | ||
514 | }, | ||
515 | ), | ||
516 | } | ||
517 | "#]] | ||
518 | .assert_debug_eq(&extract_into_function_assist); | ||
519 | } | ||
520 | } | ||
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index 969249df6..d8f23d1eb 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs | |||
@@ -737,28 +737,6 @@ fn f() {} | |||
737 | } | 737 | } |
738 | 738 | ||
739 | #[test] | 739 | #[test] |
740 | fn completes_function() { | ||
741 | check( | ||
742 | r#" | ||
743 | fn foo( | ||
744 | a: i32, | ||
745 | b: i32 | ||
746 | ) { | ||
747 | |||
748 | } | ||
749 | |||
750 | fn main() { | ||
751 | fo$0 | ||
752 | } | ||
753 | "#, | ||
754 | expect![[r#" | ||
755 | fn main() fn() | ||
756 | fn foo(…) fn(i32, i32) | ||
757 | "#]], | ||
758 | ); | ||
759 | } | ||
760 | |||
761 | #[test] | ||
762 | fn completes_self_enum() { | 740 | fn completes_self_enum() { |
763 | check( | 741 | check( |
764 | r#" | 742 | r#" |
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index b005bd773..f3fcb712c 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -301,103 +301,108 @@ impl<'a> CompletionContext<'a> { | |||
301 | .find_map(ast::Impl::cast); | 301 | .find_map(ast::Impl::cast); |
302 | } | 302 | } |
303 | 303 | ||
304 | fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) { | ||
305 | let mut node = match self.token.parent() { | ||
306 | Some(it) => it, | ||
307 | None => return (None, None), | ||
308 | }; | ||
309 | loop { | ||
310 | break match_ast! { | ||
311 | match node { | ||
312 | ast::LetStmt(it) => { | ||
313 | cov_mark::hit!(expected_type_let_with_leading_char); | ||
314 | cov_mark::hit!(expected_type_let_without_leading_char); | ||
315 | let ty = it.pat() | ||
316 | .and_then(|pat| self.sema.type_of_pat(&pat)); | ||
317 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { | ||
318 | ident.name().map(NameOrNameRef::Name) | ||
319 | } else { | ||
320 | None | ||
321 | }; | ||
322 | |||
323 | (ty, name) | ||
324 | }, | ||
325 | ast::ArgList(_it) => { | ||
326 | cov_mark::hit!(expected_type_fn_param_with_leading_char); | ||
327 | cov_mark::hit!(expected_type_fn_param_without_leading_char); | ||
328 | ActiveParameter::at_token( | ||
329 | &self.sema, | ||
330 | self.token.clone(), | ||
331 | ).map(|ap| { | ||
332 | let name = ap.ident().map(NameOrNameRef::Name); | ||
333 | (Some(ap.ty), name) | ||
334 | }) | ||
335 | .unwrap_or((None, None)) | ||
336 | }, | ||
337 | ast::RecordExprFieldList(_it) => { | ||
338 | cov_mark::hit!(expected_type_struct_field_without_leading_char); | ||
339 | self.token.prev_sibling_or_token() | ||
340 | .and_then(|se| se.into_node()) | ||
341 | .and_then(|node| ast::RecordExprField::cast(node)) | ||
342 | .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf))) | ||
343 | .map(|(f, rf)|( | ||
344 | Some(f.0.ty(self.db)), | ||
345 | rf.field_name().map(NameOrNameRef::NameRef), | ||
346 | )) | ||
347 | .unwrap_or((None, None)) | ||
348 | }, | ||
349 | ast::RecordExprField(it) => { | ||
350 | cov_mark::hit!(expected_type_struct_field_with_leading_char); | ||
351 | self.sema | ||
352 | .resolve_record_field(&it) | ||
353 | .map(|f|( | ||
354 | Some(f.0.ty(self.db)), | ||
355 | it.field_name().map(NameOrNameRef::NameRef), | ||
356 | )) | ||
357 | .unwrap_or((None, None)) | ||
358 | }, | ||
359 | ast::MatchExpr(it) => { | ||
360 | cov_mark::hit!(expected_type_match_arm_without_leading_char); | ||
361 | let ty = it.expr() | ||
362 | .and_then(|e| self.sema.type_of_expr(&e)); | ||
363 | (ty, None) | ||
364 | }, | ||
365 | ast::IfExpr(it) => { | ||
366 | cov_mark::hit!(expected_type_if_let_without_leading_char); | ||
367 | let ty = it.condition() | ||
368 | .and_then(|cond| cond.expr()) | ||
369 | .and_then(|e| self.sema.type_of_expr(&e)); | ||
370 | (ty, None) | ||
371 | }, | ||
372 | ast::IdentPat(it) => { | ||
373 | cov_mark::hit!(expected_type_if_let_with_leading_char); | ||
374 | cov_mark::hit!(expected_type_match_arm_with_leading_char); | ||
375 | let ty = self.sema.type_of_pat(&ast::Pat::from(it)); | ||
376 | (ty, None) | ||
377 | }, | ||
378 | ast::Fn(it) => { | ||
379 | cov_mark::hit!(expected_type_fn_ret_with_leading_char); | ||
380 | cov_mark::hit!(expected_type_fn_ret_without_leading_char); | ||
381 | let def = self.sema.to_def(&it); | ||
382 | (def.map(|def| def.ret_type(self.db)), None) | ||
383 | }, | ||
384 | ast::Stmt(it) => (None, None), | ||
385 | _ => { | ||
386 | match node.parent() { | ||
387 | Some(n) => { | ||
388 | node = n; | ||
389 | continue; | ||
390 | }, | ||
391 | None => (None, None), | ||
392 | } | ||
393 | }, | ||
394 | } | ||
395 | }; | ||
396 | } | ||
397 | } | ||
398 | |||
304 | fn fill( | 399 | fn fill( |
305 | &mut self, | 400 | &mut self, |
306 | original_file: &SyntaxNode, | 401 | original_file: &SyntaxNode, |
307 | file_with_fake_ident: SyntaxNode, | 402 | file_with_fake_ident: SyntaxNode, |
308 | offset: TextSize, | 403 | offset: TextSize, |
309 | ) { | 404 | ) { |
310 | let (expected_type, expected_name) = { | 405 | let (expected_type, expected_name) = self.expected_type_and_name(); |
311 | let mut node = match self.token.parent() { | ||
312 | Some(it) => it, | ||
313 | None => return, | ||
314 | }; | ||
315 | loop { | ||
316 | break match_ast! { | ||
317 | match node { | ||
318 | ast::LetStmt(it) => { | ||
319 | cov_mark::hit!(expected_type_let_with_leading_char); | ||
320 | cov_mark::hit!(expected_type_let_without_leading_char); | ||
321 | let ty = it.pat() | ||
322 | .and_then(|pat| self.sema.type_of_pat(&pat)); | ||
323 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { | ||
324 | ident.name().map(NameOrNameRef::Name) | ||
325 | } else { | ||
326 | None | ||
327 | }; | ||
328 | |||
329 | (ty, name) | ||
330 | }, | ||
331 | ast::ArgList(_it) => { | ||
332 | cov_mark::hit!(expected_type_fn_param_with_leading_char); | ||
333 | cov_mark::hit!(expected_type_fn_param_without_leading_char); | ||
334 | ActiveParameter::at_token( | ||
335 | &self.sema, | ||
336 | self.token.clone(), | ||
337 | ).map(|ap| { | ||
338 | let name = ap.ident().map(NameOrNameRef::Name); | ||
339 | (Some(ap.ty), name) | ||
340 | }) | ||
341 | .unwrap_or((None, None)) | ||
342 | }, | ||
343 | ast::RecordExprFieldList(_it) => { | ||
344 | cov_mark::hit!(expected_type_struct_field_without_leading_char); | ||
345 | self.token.prev_sibling_or_token() | ||
346 | .and_then(|se| se.into_node()) | ||
347 | .and_then(|node| ast::RecordExprField::cast(node)) | ||
348 | .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf))) | ||
349 | .map(|(f, rf)|( | ||
350 | Some(f.0.ty(self.db)), | ||
351 | rf.field_name().map(NameOrNameRef::NameRef), | ||
352 | )) | ||
353 | .unwrap_or((None, None)) | ||
354 | }, | ||
355 | ast::RecordExprField(it) => { | ||
356 | cov_mark::hit!(expected_type_struct_field_with_leading_char); | ||
357 | self.sema | ||
358 | .resolve_record_field(&it) | ||
359 | .map(|f|( | ||
360 | Some(f.0.ty(self.db)), | ||
361 | it.field_name().map(NameOrNameRef::NameRef), | ||
362 | )) | ||
363 | .unwrap_or((None, None)) | ||
364 | }, | ||
365 | ast::MatchExpr(it) => { | ||
366 | cov_mark::hit!(expected_type_match_arm_without_leading_char); | ||
367 | let ty = it.expr() | ||
368 | .and_then(|e| self.sema.type_of_expr(&e)); | ||
369 | |||
370 | (ty, None) | ||
371 | }, | ||
372 | ast::IdentPat(it) => { | ||
373 | cov_mark::hit!(expected_type_if_let_with_leading_char); | ||
374 | cov_mark::hit!(expected_type_match_arm_with_leading_char); | ||
375 | let ty = self.sema.type_of_pat(&ast::Pat::from(it)); | ||
376 | |||
377 | (ty, None) | ||
378 | }, | ||
379 | ast::Fn(_it) => { | ||
380 | cov_mark::hit!(expected_type_fn_ret_with_leading_char); | ||
381 | cov_mark::hit!(expected_type_fn_ret_without_leading_char); | ||
382 | let ty = self.token.ancestors() | ||
383 | .find_map(|ancestor| ast::Expr::cast(ancestor)) | ||
384 | .and_then(|expr| self.sema.type_of_expr(&expr)); | ||
385 | |||
386 | (ty, None) | ||
387 | }, | ||
388 | _ => { | ||
389 | match node.parent() { | ||
390 | Some(n) => { | ||
391 | node = n; | ||
392 | continue; | ||
393 | }, | ||
394 | None => (None, None), | ||
395 | } | ||
396 | }, | ||
397 | } | ||
398 | }; | ||
399 | } | ||
400 | }; | ||
401 | self.expected_type = expected_type; | 406 | self.expected_type = expected_type; |
402 | self.expected_name = expected_name; | 407 | self.expected_name = expected_name; |
403 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | 408 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); |
@@ -802,6 +807,7 @@ fn foo() { | |||
802 | 807 | ||
803 | #[test] | 808 | #[test] |
804 | fn expected_type_if_let_without_leading_char() { | 809 | fn expected_type_if_let_without_leading_char() { |
810 | cov_mark::check!(expected_type_if_let_without_leading_char); | ||
805 | check_expected_type_and_name( | 811 | check_expected_type_and_name( |
806 | r#" | 812 | r#" |
807 | enum Foo { Bar, Baz, Quux } | 813 | enum Foo { Bar, Baz, Quux } |
@@ -811,8 +817,8 @@ fn foo() { | |||
811 | if let $0 = f { } | 817 | if let $0 = f { } |
812 | } | 818 | } |
813 | "#, | 819 | "#, |
814 | expect![[r#"ty: (), name: ?"#]], | 820 | expect![[r#"ty: Foo, name: ?"#]], |
815 | ) // FIXME should be `ty: u32, name: ?` | 821 | ) |
816 | } | 822 | } |
817 | 823 | ||
818 | #[test] | 824 | #[test] |
@@ -840,8 +846,8 @@ fn foo() -> u32 { | |||
840 | $0 | 846 | $0 |
841 | } | 847 | } |
842 | "#, | 848 | "#, |
843 | expect![[r#"ty: (), name: ?"#]], | 849 | expect![[r#"ty: u32, name: ?"#]], |
844 | ) // FIXME this should be `ty: u32, name: ?` | 850 | ) |
845 | } | 851 | } |
846 | 852 | ||
847 | #[test] | 853 | #[test] |
@@ -856,4 +862,16 @@ fn foo() -> u32 { | |||
856 | expect![[r#"ty: u32, name: ?"#]], | 862 | expect![[r#"ty: u32, name: ?"#]], |
857 | ) | 863 | ) |
858 | } | 864 | } |
865 | |||
866 | #[test] | ||
867 | fn expected_type_fn_ret_fn_ref_fully_typed() { | ||
868 | check_expected_type_and_name( | ||
869 | r#" | ||
870 | fn foo() -> u32 { | ||
871 | foo$0 | ||
872 | } | ||
873 | "#, | ||
874 | expect![[r#"ty: u32, name: ?"#]], | ||
875 | ) | ||
876 | } | ||
859 | } | 877 | } |
diff --git a/crates/proc_macro_api/Cargo.toml b/crates/proc_macro_api/Cargo.toml index 1ba1e4abd..2ce5eeedd 100644 --- a/crates/proc_macro_api/Cargo.toml +++ b/crates/proc_macro_api/Cargo.toml | |||
@@ -15,7 +15,7 @@ serde_json = { version = "1.0", features = ["unbounded_depth"] } | |||
15 | log = "0.4.8" | 15 | log = "0.4.8" |
16 | crossbeam-channel = "0.5.0" | 16 | crossbeam-channel = "0.5.0" |
17 | jod-thread = "0.1.1" | 17 | jod-thread = "0.1.1" |
18 | memmap = "0.7.0" | 18 | memmap2 = "0.2.0" |
19 | object = { version = "0.23.0", default-features = false, features = ["std", "read_core", "elf", "macho", "pe", "unaligned"] } | 19 | object = { version = "0.23.0", default-features = false, features = ["std", "read_core", "elf", "macho", "pe", "unaligned"] } |
20 | snap = "1.0" | 20 | snap = "1.0" |
21 | 21 | ||
diff --git a/crates/proc_macro_api/src/version.rs b/crates/proc_macro_api/src/version.rs index dcf8fae8f..6dbac50b4 100644 --- a/crates/proc_macro_api/src/version.rs +++ b/crates/proc_macro_api/src/version.rs | |||
@@ -6,7 +6,7 @@ use std::{ | |||
6 | path::Path, | 6 | path::Path, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | use memmap::Mmap; | 9 | use memmap2::Mmap; |
10 | use object::read::{File as BinaryFile, Object, ObjectSection}; | 10 | use object::read::{File as BinaryFile, Object, ObjectSection}; |
11 | use snap::read::FrameDecoder as SnapDecoder; | 11 | use snap::read::FrameDecoder as SnapDecoder; |
12 | 12 | ||
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index 74f784338..c33c8179c 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs | |||
@@ -7,7 +7,7 @@ use anyhow::anyhow; | |||
7 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
8 | 8 | ||
9 | use hir::{db::HirDatabase, Crate, Module}; | 9 | use hir::{db::HirDatabase, Crate, Module}; |
10 | use ide::{DiagnosticsConfig, Severity}; | 10 | use ide::{AssistResolveStrategy, DiagnosticsConfig, Severity}; |
11 | use ide_db::base_db::SourceDatabaseExt; | 11 | use ide_db::base_db::SourceDatabaseExt; |
12 | 12 | ||
13 | use crate::cli::{ | 13 | use crate::cli::{ |
@@ -57,8 +57,9 @@ pub fn diagnostics( | |||
57 | let crate_name = | 57 | let crate_name = |
58 | module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string(); | 58 | module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string(); |
59 | println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); | 59 | println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); |
60 | for diagnostic in | 60 | for diagnostic in analysis |
61 | analysis.diagnostics(&DiagnosticsConfig::default(), false, file_id).unwrap() | 61 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::None, file_id) |
62 | .unwrap() | ||
62 | { | 63 | { |
63 | if matches!(diagnostic.severity, Severity::Error) { | 64 | if matches!(diagnostic.severity, Severity::Error) { |
64 | found_error = true; | 65 | found_error = true; |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 1f59402e5..f6e40f872 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -8,8 +8,9 @@ use std::{ | |||
8 | }; | 8 | }; |
9 | 9 | ||
10 | use ide::{ | 10 | use ide::{ |
11 | AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, Query, | 11 | AnnotationConfig, AssistKind, AssistResolveStrategy, FileId, FilePosition, FileRange, |
12 | RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, | 12 | HoverAction, HoverGotoTypeData, Query, RangeInfo, Runnable, RunnableKind, SearchScope, |
13 | SingleResolve, SourceChange, TextEdit, | ||
13 | }; | 14 | }; |
14 | use ide_db::SymbolKind; | 15 | use ide_db::SymbolKind; |
15 | use itertools::Itertools; | 16 | use itertools::Itertools; |
@@ -27,7 +28,7 @@ use lsp_types::{ | |||
27 | use project_model::TargetKind; | 28 | use project_model::TargetKind; |
28 | use serde::{Deserialize, Serialize}; | 29 | use serde::{Deserialize, Serialize}; |
29 | use serde_json::to_value; | 30 | use serde_json::to_value; |
30 | use stdx::{format_to, split_once}; | 31 | use stdx::format_to; |
31 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; | 32 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; |
32 | 33 | ||
33 | use crate::{ | 34 | use crate::{ |
@@ -1004,10 +1005,15 @@ pub(crate) fn handle_code_action( | |||
1004 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); | 1005 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); |
1005 | 1006 | ||
1006 | let code_action_resolve_cap = snap.config.code_action_resolve(); | 1007 | let code_action_resolve_cap = snap.config.code_action_resolve(); |
1008 | let resolve = if code_action_resolve_cap { | ||
1009 | AssistResolveStrategy::None | ||
1010 | } else { | ||
1011 | AssistResolveStrategy::All | ||
1012 | }; | ||
1007 | let assists = snap.analysis.assists_with_fixes( | 1013 | let assists = snap.analysis.assists_with_fixes( |
1008 | &assists_config, | 1014 | &assists_config, |
1009 | &snap.config.diagnostics(), | 1015 | &snap.config.diagnostics(), |
1010 | !code_action_resolve_cap, | 1016 | resolve, |
1011 | frange, | 1017 | frange, |
1012 | )?; | 1018 | )?; |
1013 | for (index, assist) in assists.into_iter().enumerate() { | 1019 | for (index, assist) in assists.into_iter().enumerate() { |
@@ -1052,22 +1058,68 @@ pub(crate) fn handle_code_action_resolve( | |||
1052 | .only | 1058 | .only |
1053 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); | 1059 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); |
1054 | 1060 | ||
1061 | let (assist_index, assist_resolve) = match parse_action_id(¶ms.id) { | ||
1062 | Ok(parsed_data) => parsed_data, | ||
1063 | Err(e) => { | ||
1064 | return Err(LspError::new( | ||
1065 | ErrorCode::InvalidParams as i32, | ||
1066 | format!("Failed to parse action id string '{}': {}", params.id, e), | ||
1067 | ) | ||
1068 | .into()) | ||
1069 | } | ||
1070 | }; | ||
1071 | |||
1072 | let expected_assist_id = assist_resolve.assist_id.clone(); | ||
1073 | let expected_kind = assist_resolve.assist_kind; | ||
1074 | |||
1055 | let assists = snap.analysis.assists_with_fixes( | 1075 | let assists = snap.analysis.assists_with_fixes( |
1056 | &assists_config, | 1076 | &assists_config, |
1057 | &snap.config.diagnostics(), | 1077 | &snap.config.diagnostics(), |
1058 | true, | 1078 | AssistResolveStrategy::Single(assist_resolve), |
1059 | frange, | 1079 | frange, |
1060 | )?; | 1080 | )?; |
1061 | 1081 | ||
1062 | let (id, index) = split_once(¶ms.id, ':').unwrap(); | 1082 | let assist = match assists.get(assist_index) { |
1063 | let index = index.parse::<usize>().unwrap(); | 1083 | Some(assist) => assist, |
1064 | let assist = &assists[index]; | 1084 | None => return Err(LspError::new( |
1065 | assert!(assist.id.0 == id); | 1085 | ErrorCode::InvalidParams as i32, |
1086 | format!( | ||
1087 | "Failed to find the assist for index {} provided by the resolve request. Resolve request assist id: {}", | ||
1088 | assist_index, params.id, | ||
1089 | ), | ||
1090 | ) | ||
1091 | .into()) | ||
1092 | }; | ||
1093 | if assist.id.0 != expected_assist_id || assist.id.1 != expected_kind { | ||
1094 | return Err(LspError::new( | ||
1095 | ErrorCode::InvalidParams as i32, | ||
1096 | format!( | ||
1097 | "Mismatching assist at index {} for the resolve parameters given. Resolve request assist id: {}, actual id: {:?}.", | ||
1098 | assist_index, params.id, assist.id | ||
1099 | ), | ||
1100 | ) | ||
1101 | .into()); | ||
1102 | } | ||
1066 | let edit = to_proto::code_action(&snap, assist.clone(), None)?.edit; | 1103 | let edit = to_proto::code_action(&snap, assist.clone(), None)?.edit; |
1067 | code_action.edit = edit; | 1104 | code_action.edit = edit; |
1068 | Ok(code_action) | 1105 | Ok(code_action) |
1069 | } | 1106 | } |
1070 | 1107 | ||
1108 | fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> { | ||
1109 | let id_parts = action_id.split(':').collect_vec(); | ||
1110 | match id_parts.as_slice() { | ||
1111 | &[assist_id_string, assist_kind_string, index_string] => { | ||
1112 | let assist_kind: AssistKind = assist_kind_string.parse()?; | ||
1113 | let index: usize = match index_string.parse() { | ||
1114 | Ok(index) => index, | ||
1115 | Err(e) => return Err(format!("Incorrect index string: {}", e)), | ||
1116 | }; | ||
1117 | Ok((index, SingleResolve { assist_id: assist_id_string.to_string(), assist_kind })) | ||
1118 | } | ||
1119 | _ => Err("Action id contains incorrect number of segments".to_string()), | ||
1120 | } | ||
1121 | } | ||
1122 | |||
1071 | pub(crate) fn handle_code_lens( | 1123 | pub(crate) fn handle_code_lens( |
1072 | snap: GlobalStateSnapshot, | 1124 | snap: GlobalStateSnapshot, |
1073 | params: lsp_types::CodeLensParams, | 1125 | params: lsp_types::CodeLensParams, |
@@ -1182,7 +1234,7 @@ pub(crate) fn publish_diagnostics( | |||
1182 | 1234 | ||
1183 | let diagnostics: Vec<Diagnostic> = snap | 1235 | let diagnostics: Vec<Diagnostic> = snap |
1184 | .analysis | 1236 | .analysis |
1185 | .diagnostics(&snap.config.diagnostics(), false, file_id)? | 1237 | .diagnostics(&snap.config.diagnostics(), AssistResolveStrategy::None, file_id)? |
1186 | .into_iter() | 1238 | .into_iter() |
1187 | .map(|d| Diagnostic { | 1239 | .map(|d| Diagnostic { |
1188 | range: to_proto::range(&line_index, d.range), | 1240 | range: to_proto::range(&line_index, d.range), |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index c2361b32e..1d27aa7b3 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -897,7 +897,7 @@ pub(crate) fn code_action( | |||
897 | (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?), | 897 | (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?), |
898 | (None, Some((index, code_action_params))) => { | 898 | (None, Some((index, code_action_params))) => { |
899 | res.data = Some(lsp_ext::CodeActionData { | 899 | res.data = Some(lsp_ext::CodeActionData { |
900 | id: format!("{}:{}", assist.id.0, index.to_string()), | 900 | id: format!("{}:{}:{}", assist.id.0, assist.id.1.name(), index), |
901 | code_action_params, | 901 | code_action_params, |
902 | }); | 902 | }); |
903 | } | 903 | } |