diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 60 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 38 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unlinked_file.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 18 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 32 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tests.rs | 98 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/flip_comma.rs | 18 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/build.rs | 3 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/diagnostics.rs | 3 | ||||
-rw-r--r-- | crates/rust-analyzer/src/from_proto.rs | 30 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 102 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 39 | ||||
-rw-r--r-- | crates/rust-analyzer/tests/rust-analyzer/main.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/tests/rust-analyzer/support.rs | 1 | ||||
-rw-r--r-- | crates/test_utils/src/assert_linear.rs | 112 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 3 |
17 files changed, 313 insertions, 250 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 { | |||
84 | pub(crate) fn diagnostics( | 84 | pub(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 | ||
211 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 212 | fn diagnostic_with_fix<D: DiagnosticWithFix>( |
213 | d: &D, | ||
214 | sema: &Semantics<RootDatabase>, | ||
215 | resolve: bool, | ||
216 | ) -> Diagnostic { | ||
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 | ||
217 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 222 | fn warning_with_fix<D: DiagnosticWithFix>( |
223 | d: &D, | ||
224 | sema: &Semantics<RootDatabase>, | ||
225 | resolve: bool, | ||
226 | ) -> Diagnostic { | ||
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 | ||
273 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | 282 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { |
283 | let mut res = unresolved_fix(id, label, target); | ||
284 | res.source_change = Some(source_change); | ||
285 | res | ||
286 | } | ||
287 | |||
288 | fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | ||
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 | }; |
21 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
22 | 22 | ||
23 | use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, FilePosition}; | 23 | use crate::{ |
24 | diagnostics::{fix, unresolved_fix}, | ||
25 | references::rename::rename_with_semantics, | ||
26 | Assist, FilePosition, | ||
27 | }; | ||
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 |
28 | pub(crate) trait DiagnosticWithFix: Diagnostic { | 32 | pub(crate) trait DiagnosticWithFix: Diagnostic { |
29 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist>; | 33 | /// `resolve` determines if the diagnostic should fill in the `edit` field |
34 | /// of the assist. | ||
35 | /// | ||
36 | /// If `resolve` is false, the edit will be computed later, on demand, and | ||
37 | /// can be omitted. | ||
38 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; | ||
30 | } | 39 | } |
31 | 40 | ||
32 | impl DiagnosticWithFix for UnresolvedModule { | 41 | impl 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 | ||
52 | impl DiagnosticWithFix for NoSuchField { | 61 | impl 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 | ||
63 | impl DiagnosticWithFix for MissingFields { | 72 | impl 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 | ||
99 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | 108 | impl 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 | ||
112 | impl DiagnosticWithFix for RemoveThisSemicolon { | 121 | impl 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 | ||
132 | impl DiagnosticWithFix for IncorrectCase { | 141 | impl 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 | ||
149 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | 161 | impl 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 | ||
52 | impl DiagnosticWithFix for UnlinkedFile { | 52 | impl DiagnosticWithFix for UnlinkedFile { |
53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Assist> { | 53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
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/goto_definition.rs b/crates/ide/src/goto_definition.rs index d057d5402..a04333e63 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -110,6 +110,13 @@ mod tests { | |||
110 | assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); | 110 | assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); |
111 | } | 111 | } |
112 | 112 | ||
113 | fn check_unresolved(ra_fixture: &str) { | ||
114 | let (analysis, position) = fixture::position(ra_fixture); | ||
115 | let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; | ||
116 | |||
117 | assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs) | ||
118 | } | ||
119 | |||
113 | #[test] | 120 | #[test] |
114 | fn goto_def_for_extern_crate() { | 121 | fn goto_def_for_extern_crate() { |
115 | check( | 122 | check( |
@@ -927,17 +934,12 @@ fn f() -> impl Iterator<Item$0 = u8> {} | |||
927 | } | 934 | } |
928 | 935 | ||
929 | #[test] | 936 | #[test] |
930 | #[should_panic = "unresolved reference"] | ||
931 | fn unknown_assoc_ty() { | 937 | fn unknown_assoc_ty() { |
932 | check( | 938 | check_unresolved( |
933 | r#" | 939 | r#" |
934 | trait Iterator { | 940 | trait Iterator { type Item; } |
935 | type Item; | ||
936 | //^^^^ | ||
937 | } | ||
938 | |||
939 | fn f() -> impl Iterator<Invalid$0 = u8> {} | 941 | fn f() -> impl Iterator<Invalid$0 = u8> {} |
940 | "#, | 942 | "#, |
941 | ) | 943 | ) |
942 | } | 944 | } |
943 | 945 | ||
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 |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index de2d22ac7..933cfa6f3 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -2,8 +2,7 @@ use std::time::Instant; | |||
2 | 2 | ||
3 | use expect_test::{expect_file, ExpectFile}; | 3 | use expect_test::{expect_file, ExpectFile}; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use stdx::format_to; | 5 | use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear}; |
6 | use test_utils::{bench, bench_fixture, skip_slow_tests}; | ||
7 | 6 | ||
8 | use crate::{fixture, FileRange, HlTag, TextRange}; | 7 | use crate::{fixture, FileRange, HlTag, TextRange}; |
9 | 8 | ||
@@ -266,90 +265,27 @@ fn syntax_highlighting_not_quadratic() { | |||
266 | return; | 265 | return; |
267 | } | 266 | } |
268 | 267 | ||
269 | let mut measures = Vec::new(); | 268 | let mut al = AssertLinear::default(); |
270 | for i in 6..=10 { | 269 | while al.next_round() { |
271 | let n = 1 << i; | 270 | for i in 6..=10 { |
272 | let fixture = bench_fixture::big_struct_n(n); | 271 | let n = 1 << i; |
273 | let (analysis, file_id) = fixture::file(&fixture); | ||
274 | 272 | ||
275 | let time = Instant::now(); | 273 | let fixture = bench_fixture::big_struct_n(n); |
274 | let (analysis, file_id) = fixture::file(&fixture); | ||
276 | 275 | ||
277 | let hash = analysis | 276 | let time = Instant::now(); |
278 | .highlight(file_id) | ||
279 | .unwrap() | ||
280 | .iter() | ||
281 | .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) | ||
282 | .count(); | ||
283 | assert!(hash > n as usize); | ||
284 | 277 | ||
285 | let elapsed = time.elapsed(); | 278 | let hash = analysis |
286 | measures.push((n as f64, elapsed.as_millis() as f64)) | 279 | .highlight(file_id) |
287 | } | 280 | .unwrap() |
281 | .iter() | ||
282 | .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) | ||
283 | .count(); | ||
284 | assert!(hash > n as usize); | ||
288 | 285 | ||
289 | assert_linear(&measures) | 286 | let elapsed = time.elapsed(); |
290 | } | 287 | al.sample(n as f64, elapsed.as_millis() as f64); |
291 | |||
292 | /// Checks that a set of measurements looks like a liner function rather than | ||
293 | /// like a quadratic function. Algorithm: | ||
294 | /// | ||
295 | /// 1. Linearly scale input to be in [0; 1) | ||
296 | /// 2. Using linear regression, compute the best linear function approximating | ||
297 | /// the input. | ||
298 | /// 3. Compute RMSE and maximal absolute error. | ||
299 | /// 4. Check that errors are within tolerances and that the constant term is not | ||
300 | /// too negative. | ||
301 | /// | ||
302 | /// Ideally, we should use a proper "model selection" to directly compare | ||
303 | /// quadratic and linear models, but that sounds rather complicated: | ||
304 | /// | ||
305 | /// https://stats.stackexchange.com/questions/21844/selecting-best-model-based-on-linear-quadratic-and-cubic-fit-of-data | ||
306 | fn assert_linear(xy: &[(f64, f64)]) { | ||
307 | let (mut xs, mut ys): (Vec<_>, Vec<_>) = xy.iter().copied().unzip(); | ||
308 | normalize(&mut xs); | ||
309 | normalize(&mut ys); | ||
310 | let xy = xs.iter().copied().zip(ys.iter().copied()); | ||
311 | |||
312 | // Linear regression: finding a and b to fit y = a + b*x. | ||
313 | |||
314 | let mean_x = mean(&xs); | ||
315 | let mean_y = mean(&ys); | ||
316 | |||
317 | let b = { | ||
318 | let mut num = 0.0; | ||
319 | let mut denom = 0.0; | ||
320 | for (x, y) in xy.clone() { | ||
321 | num += (x - mean_x) * (y - mean_y); | ||
322 | denom += (x - mean_x).powi(2); | ||
323 | } | 288 | } |
324 | num / denom | ||
325 | }; | ||
326 | |||
327 | let a = mean_y - b * mean_x; | ||
328 | |||
329 | let mut plot = format!("y_pred = {:.3} + {:.3} * x\n\nx y y_pred\n", a, b); | ||
330 | |||
331 | let mut se = 0.0; | ||
332 | let mut max_error = 0.0f64; | ||
333 | for (x, y) in xy { | ||
334 | let y_pred = a + b * x; | ||
335 | se += (y - y_pred).powi(2); | ||
336 | max_error = max_error.max((y_pred - y).abs()); | ||
337 | |||
338 | format_to!(plot, "{:.3} {:.3} {:.3}\n", x, y, y_pred); | ||
339 | } | ||
340 | |||
341 | let rmse = (se / xs.len() as f64).sqrt(); | ||
342 | format_to!(plot, "\nrmse = {:.3} max error = {:.3}", rmse, max_error); | ||
343 | |||
344 | assert!(rmse < 0.05 && max_error < 0.1 && a > -0.1, "\nLooks quadratic\n{}", plot); | ||
345 | |||
346 | fn normalize(xs: &mut Vec<f64>) { | ||
347 | let max = xs.iter().copied().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap(); | ||
348 | xs.iter_mut().for_each(|it| *it /= max); | ||
349 | } | ||
350 | |||
351 | fn mean(xs: &[f64]) -> f64 { | ||
352 | xs.iter().copied().sum::<f64>() / (xs.len() as f64) | ||
353 | } | 289 | } |
354 | } | 290 | } |
355 | 291 | ||
diff --git a/crates/ide_assists/src/handlers/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs index 7f5e75d34..99be5e090 100644 --- a/crates/ide_assists/src/handlers/flip_comma.rs +++ b/crates/ide_assists/src/handlers/flip_comma.rs | |||
@@ -66,26 +66,12 @@ mod tests { | |||
66 | } | 66 | } |
67 | 67 | ||
68 | #[test] | 68 | #[test] |
69 | #[should_panic] | ||
70 | fn flip_comma_before_punct() { | 69 | fn flip_comma_before_punct() { |
71 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 | 70 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 |
72 | // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct | 71 | // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct |
73 | // declaration body. | 72 | // declaration body. |
74 | check_assist_target( | 73 | check_assist_not_applicable(flip_comma, "pub enum Test { A,$0 }"); |
75 | flip_comma, | 74 | check_assist_not_applicable(flip_comma, "pub struct Test { foo: usize,$0 }"); |
76 | "pub enum Test { \ | ||
77 | A,$0 \ | ||
78 | }", | ||
79 | ",", | ||
80 | ); | ||
81 | |||
82 | check_assist_target( | ||
83 | flip_comma, | ||
84 | "pub struct Test { \ | ||
85 | foo: usize,$0 \ | ||
86 | }", | ||
87 | ",", | ||
88 | ); | ||
89 | } | 75 | } |
90 | 76 | ||
91 | #[test] | 77 | #[test] |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 3e2c82dac..3694f468f 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -28,7 +28,9 @@ pub use assist_config::AssistConfig; | |||
28 | 28 | ||
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
30 | pub enum AssistKind { | 30 | pub enum AssistKind { |
31 | // FIXME: does the None variant make sense? Probably not. | ||
31 | None, | 32 | None, |
33 | |||
32 | QuickFix, | 34 | QuickFix, |
33 | Generate, | 35 | Generate, |
34 | Refactor, | 36 | Refactor, |
diff --git a/crates/rust-analyzer/build.rs b/crates/rust-analyzer/build.rs index 999dc5928..13b903891 100644 --- a/crates/rust-analyzer/build.rs +++ b/crates/rust-analyzer/build.rs | |||
@@ -39,7 +39,8 @@ fn set_rerun() { | |||
39 | } | 39 | } |
40 | 40 | ||
41 | fn rev() -> Option<String> { | 41 | fn rev() -> Option<String> { |
42 | let output = Command::new("git").args(&["describe", "--tags"]).output().ok()?; | 42 | let output = |
43 | Command::new("git").args(&["describe", "--tags", "--exclude", "nightly"]).output().ok()?; | ||
43 | let stdout = String::from_utf8(output.stdout).ok()?; | 44 | let stdout = String::from_utf8(output.stdout).ok()?; |
44 | Some(stdout) | 45 | Some(stdout) |
45 | } | 46 | } |
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index 0085d0e4d..74f784338 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs | |||
@@ -57,7 +57,8 @@ 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 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() | 60 | for diagnostic in |
61 | analysis.diagnostics(&DiagnosticsConfig::default(), false, file_id).unwrap() | ||
61 | { | 62 | { |
62 | if matches!(diagnostic.severity, Severity::Error) { | 63 | if matches!(diagnostic.severity, Severity::Error) { |
63 | found_error = true; | 64 | found_error = true; |
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 5b02b2598..712d5a9c2 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs | |||
@@ -42,27 +42,27 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex | |||
42 | TextRange::new(start, end) | 42 | TextRange::new(start, end) |
43 | } | 43 | } |
44 | 44 | ||
45 | pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { | 45 | pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { |
46 | world.url_to_file_id(url) | 46 | snap.url_to_file_id(url) |
47 | } | 47 | } |
48 | 48 | ||
49 | pub(crate) fn file_position( | 49 | pub(crate) fn file_position( |
50 | world: &GlobalStateSnapshot, | 50 | snap: &GlobalStateSnapshot, |
51 | tdpp: lsp_types::TextDocumentPositionParams, | 51 | tdpp: lsp_types::TextDocumentPositionParams, |
52 | ) -> Result<FilePosition> { | 52 | ) -> Result<FilePosition> { |
53 | let file_id = file_id(world, &tdpp.text_document.uri)?; | 53 | let file_id = file_id(snap, &tdpp.text_document.uri)?; |
54 | let line_index = world.file_line_index(file_id)?; | 54 | let line_index = snap.file_line_index(file_id)?; |
55 | let offset = offset(&line_index, tdpp.position); | 55 | let offset = offset(&line_index, tdpp.position); |
56 | Ok(FilePosition { file_id, offset }) | 56 | Ok(FilePosition { file_id, offset }) |
57 | } | 57 | } |
58 | 58 | ||
59 | pub(crate) fn file_range( | 59 | pub(crate) fn file_range( |
60 | world: &GlobalStateSnapshot, | 60 | snap: &GlobalStateSnapshot, |
61 | text_document_identifier: lsp_types::TextDocumentIdentifier, | 61 | text_document_identifier: lsp_types::TextDocumentIdentifier, |
62 | range: lsp_types::Range, | 62 | range: lsp_types::Range, |
63 | ) -> Result<FileRange> { | 63 | ) -> Result<FileRange> { |
64 | let file_id = file_id(world, &text_document_identifier.uri)?; | 64 | let file_id = file_id(snap, &text_document_identifier.uri)?; |
65 | let line_index = world.file_line_index(file_id)?; | 65 | let line_index = snap.file_line_index(file_id)?; |
66 | let range = text_range(&line_index, range); | 66 | let range = text_range(&line_index, range); |
67 | Ok(FileRange { file_id, range }) | 67 | Ok(FileRange { file_id, range }) |
68 | } | 68 | } |
@@ -82,7 +82,7 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> | |||
82 | } | 82 | } |
83 | 83 | ||
84 | pub(crate) fn annotation( | 84 | pub(crate) fn annotation( |
85 | world: &GlobalStateSnapshot, | 85 | snap: &GlobalStateSnapshot, |
86 | code_lens: lsp_types::CodeLens, | 86 | code_lens: lsp_types::CodeLens, |
87 | ) -> Result<Annotation> { | 87 | ) -> Result<Annotation> { |
88 | let data = code_lens.data.unwrap(); | 88 | let data = code_lens.data.unwrap(); |
@@ -91,25 +91,25 @@ pub(crate) fn annotation( | |||
91 | match resolve { | 91 | match resolve { |
92 | lsp_ext::CodeLensResolveData::Impls(params) => { | 92 | lsp_ext::CodeLensResolveData::Impls(params) => { |
93 | let file_id = | 93 | let file_id = |
94 | world.url_to_file_id(¶ms.text_document_position_params.text_document.uri)?; | 94 | snap.url_to_file_id(¶ms.text_document_position_params.text_document.uri)?; |
95 | let line_index = world.file_line_index(file_id)?; | 95 | let line_index = snap.file_line_index(file_id)?; |
96 | 96 | ||
97 | Ok(Annotation { | 97 | Ok(Annotation { |
98 | range: text_range(&line_index, code_lens.range), | 98 | range: text_range(&line_index, code_lens.range), |
99 | kind: AnnotationKind::HasImpls { | 99 | kind: AnnotationKind::HasImpls { |
100 | position: file_position(world, params.text_document_position_params)?, | 100 | position: file_position(snap, params.text_document_position_params)?, |
101 | data: None, | 101 | data: None, |
102 | }, | 102 | }, |
103 | }) | 103 | }) |
104 | } | 104 | } |
105 | lsp_ext::CodeLensResolveData::References(params) => { | 105 | lsp_ext::CodeLensResolveData::References(params) => { |
106 | let file_id = world.url_to_file_id(¶ms.text_document.uri)?; | 106 | let file_id = snap.url_to_file_id(¶ms.text_document.uri)?; |
107 | let line_index = world.file_line_index(file_id)?; | 107 | let line_index = snap.file_line_index(file_id)?; |
108 | 108 | ||
109 | Ok(Annotation { | 109 | Ok(Annotation { |
110 | range: text_range(&line_index, code_lens.range), | 110 | range: text_range(&line_index, code_lens.range), |
111 | kind: AnnotationKind::HasReferences { | 111 | kind: AnnotationKind::HasReferences { |
112 | position: file_position(world, params)?, | 112 | position: file_position(snap, params)?, |
113 | data: None, | 113 | data: None, |
114 | }, | 114 | }, |
115 | }) | 115 | }) |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 107685c63..4f0c9d23c 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -17,7 +17,7 @@ use lsp_server::ErrorCode; | |||
17 | use lsp_types::{ | 17 | use lsp_types::{ |
18 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 18 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
19 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | 19 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, |
20 | CodeActionKind, CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, | 20 | CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, |
21 | DocumentHighlight, FoldingRange, FoldingRangeParams, HoverContents, Location, NumberOrString, | 21 | DocumentHighlight, FoldingRange, FoldingRangeParams, HoverContents, Location, NumberOrString, |
22 | Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams, | 22 | Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams, |
23 | SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams, | 23 | SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams, |
@@ -36,7 +36,7 @@ use crate::{ | |||
36 | diff::diff, | 36 | diff::diff, |
37 | from_proto, | 37 | from_proto, |
38 | global_state::{GlobalState, GlobalStateSnapshot}, | 38 | global_state::{GlobalState, GlobalStateSnapshot}, |
39 | line_index::{LineEndings, LineIndex}, | 39 | line_index::LineEndings, |
40 | lsp_ext::{self, InlayHint, InlayHintsParams}, | 40 | lsp_ext::{self, InlayHint, InlayHintsParams}, |
41 | lsp_utils::all_edits_are_disjoint, | 41 | lsp_utils::all_edits_are_disjoint, |
42 | to_proto, LspError, Result, | 42 | to_proto, LspError, Result, |
@@ -982,86 +982,52 @@ pub(crate) fn handle_code_action( | |||
982 | params: lsp_types::CodeActionParams, | 982 | params: lsp_types::CodeActionParams, |
983 | ) -> Result<Option<Vec<lsp_ext::CodeAction>>> { | 983 | ) -> Result<Option<Vec<lsp_ext::CodeAction>>> { |
984 | let _p = profile::span("handle_code_action"); | 984 | let _p = profile::span("handle_code_action"); |
985 | // We intentionally don't support command-based actions, as those either | 985 | |
986 | // requires custom client-code anyway, or requires server-initiated edits. | ||
987 | // Server initiated edits break causality, so we avoid those as well. | ||
988 | if !snap.config.code_action_literals() { | 986 | if !snap.config.code_action_literals() { |
987 | // We intentionally don't support command-based actions, as those either | ||
988 | // require either custom client-code or server-initiated edits. Server | ||
989 | // initiated edits break causality, so we avoid those. | ||
989 | return Ok(None); | 990 | return Ok(None); |
990 | } | 991 | } |
991 | 992 | ||
992 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | 993 | let line_index = |
993 | let line_index = snap.file_line_index(file_id)?; | 994 | snap.file_line_index(from_proto::file_id(&snap, ¶ms.text_document.uri)?)?; |
994 | let range = from_proto::text_range(&line_index, params.range); | 995 | let frange = from_proto::file_range(&snap, params.text_document.clone(), params.range)?; |
995 | let frange = FileRange { file_id, range }; | ||
996 | 996 | ||
997 | let mut assists_config = snap.config.assist(); | 997 | let mut assists_config = snap.config.assist(); |
998 | assists_config.allowed = params | 998 | assists_config.allowed = params |
999 | .clone() | ||
1000 | .context | 999 | .context |
1001 | .only | 1000 | .only |
1001 | .clone() | ||
1002 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); | 1002 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); |
1003 | 1003 | ||
1004 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); | 1004 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); |
1005 | 1005 | ||
1006 | let include_quick_fixes = match ¶ms.context.only { | 1006 | let code_action_resolve_cap = snap.config.code_action_resolve(); |
1007 | Some(v) => v.iter().any(|it| { | 1007 | let assists = snap.analysis.assists_with_fixes( |
1008 | it == &lsp_types::CodeActionKind::EMPTY || it == &lsp_types::CodeActionKind::QUICKFIX | 1008 | &assists_config, |
1009 | }), | 1009 | &snap.config.diagnostics(), |
1010 | None => true, | 1010 | !code_action_resolve_cap, |
1011 | }; | 1011 | frange, |
1012 | if include_quick_fixes { | 1012 | )?; |
1013 | add_quick_fixes(&snap, frange, &line_index, &mut res)?; | 1013 | for (index, assist) in assists.into_iter().enumerate() { |
1014 | } | 1014 | let resolve_data = |
1015 | 1015 | if code_action_resolve_cap { Some((index, params.clone())) } else { None }; | |
1016 | if snap.config.code_action_resolve() { | 1016 | let code_action = to_proto::code_action(&snap, assist, resolve_data)?; |
1017 | for (index, assist) in | 1017 | res.push(code_action) |
1018 | snap.analysis.assists(&assists_config, false, frange)?.into_iter().enumerate() | ||
1019 | { | ||
1020 | res.push(to_proto::unresolved_code_action(&snap, params.clone(), assist, index)?); | ||
1021 | } | ||
1022 | } else { | ||
1023 | for assist in snap.analysis.assists(&assists_config, true, frange)?.into_iter() { | ||
1024 | res.push(to_proto::resolved_code_action(&snap, assist)?); | ||
1025 | } | ||
1026 | } | ||
1027 | |||
1028 | Ok(Some(res)) | ||
1029 | } | ||
1030 | |||
1031 | fn add_quick_fixes( | ||
1032 | snap: &GlobalStateSnapshot, | ||
1033 | frange: FileRange, | ||
1034 | line_index: &LineIndex, | ||
1035 | acc: &mut Vec<lsp_ext::CodeAction>, | ||
1036 | ) -> Result<()> { | ||
1037 | let diagnostics = snap.analysis.diagnostics(&snap.config.diagnostics(), frange.file_id)?; | ||
1038 | |||
1039 | for fix in diagnostics | ||
1040 | .into_iter() | ||
1041 | .filter_map(|d| d.fix) | ||
1042 | .filter(|fix| fix.target.intersect(frange.range).is_some()) | ||
1043 | { | ||
1044 | if let Some(source_change) = fix.source_change { | ||
1045 | let edit = to_proto::snippet_workspace_edit(&snap, source_change)?; | ||
1046 | let action = lsp_ext::CodeAction { | ||
1047 | title: fix.label.to_string(), | ||
1048 | group: None, | ||
1049 | kind: Some(CodeActionKind::QUICKFIX), | ||
1050 | edit: Some(edit), | ||
1051 | is_preferred: Some(false), | ||
1052 | data: None, | ||
1053 | }; | ||
1054 | acc.push(action); | ||
1055 | } | ||
1056 | } | 1018 | } |
1057 | 1019 | ||
1020 | // Fixes from `cargo check`. | ||
1058 | for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() { | 1021 | for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() { |
1022 | // FIXME: this mapping is awkward and shouldn't exist. Refactor | ||
1023 | // `snap.check_fixes` to not convert to LSP prematurely. | ||
1059 | let fix_range = from_proto::text_range(&line_index, fix.range); | 1024 | let fix_range = from_proto::text_range(&line_index, fix.range); |
1060 | if fix_range.intersect(frange.range).is_some() { | 1025 | if fix_range.intersect(frange.range).is_some() { |
1061 | acc.push(fix.action.clone()); | 1026 | res.push(fix.action.clone()); |
1062 | } | 1027 | } |
1063 | } | 1028 | } |
1064 | Ok(()) | 1029 | |
1030 | Ok(Some(res)) | ||
1065 | } | 1031 | } |
1066 | 1032 | ||
1067 | pub(crate) fn handle_code_action_resolve( | 1033 | pub(crate) fn handle_code_action_resolve( |
@@ -1086,12 +1052,18 @@ pub(crate) fn handle_code_action_resolve( | |||
1086 | .only | 1052 | .only |
1087 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); | 1053 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); |
1088 | 1054 | ||
1089 | let assists = snap.analysis.assists(&assists_config, true, frange)?; | 1055 | let assists = snap.analysis.assists_with_fixes( |
1056 | &assists_config, | ||
1057 | &snap.config.diagnostics(), | ||
1058 | true, | ||
1059 | frange, | ||
1060 | )?; | ||
1061 | |||
1090 | let (id, index) = split_once(¶ms.id, ':').unwrap(); | 1062 | let (id, index) = split_once(¶ms.id, ':').unwrap(); |
1091 | let index = index.parse::<usize>().unwrap(); | 1063 | let index = index.parse::<usize>().unwrap(); |
1092 | let assist = &assists[index]; | 1064 | let assist = &assists[index]; |
1093 | assert!(assist.id.0 == id); | 1065 | assert!(assist.id.0 == id); |
1094 | let edit = to_proto::resolved_code_action(&snap, assist.clone())?.edit; | 1066 | let edit = to_proto::code_action(&snap, assist.clone(), None)?.edit; |
1095 | code_action.edit = edit; | 1067 | code_action.edit = edit; |
1096 | Ok(code_action) | 1068 | Ok(code_action) |
1097 | } | 1069 | } |
@@ -1210,7 +1182,7 @@ pub(crate) fn publish_diagnostics( | |||
1210 | 1182 | ||
1211 | let diagnostics: Vec<Diagnostic> = snap | 1183 | let diagnostics: Vec<Diagnostic> = snap |
1212 | .analysis | 1184 | .analysis |
1213 | .diagnostics(&snap.config.diagnostics(), file_id)? | 1185 | .diagnostics(&snap.config.diagnostics(), false, file_id)? |
1214 | .into_iter() | 1186 | .into_iter() |
1215 | .map(|d| Diagnostic { | 1187 | .map(|d| Diagnostic { |
1216 | range: to_proto::range(&line_index, d.range), | 1188 | 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 9fac562ff..53852bfdc 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -843,40 +843,31 @@ pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind { | |||
843 | } | 843 | } |
844 | } | 844 | } |
845 | 845 | ||
846 | pub(crate) fn unresolved_code_action( | 846 | pub(crate) fn code_action( |
847 | snap: &GlobalStateSnapshot, | 847 | snap: &GlobalStateSnapshot, |
848 | code_action_params: lsp_types::CodeActionParams, | ||
849 | assist: Assist, | 848 | assist: Assist, |
850 | index: usize, | 849 | resolve_data: Option<(usize, lsp_types::CodeActionParams)>, |
851 | ) -> Result<lsp_ext::CodeAction> { | 850 | ) -> Result<lsp_ext::CodeAction> { |
852 | assert!(assist.source_change.is_none()); | 851 | let mut res = lsp_ext::CodeAction { |
853 | let res = lsp_ext::CodeAction { | ||
854 | title: assist.label.to_string(), | 852 | title: assist.label.to_string(), |
855 | group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0), | 853 | group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0), |
856 | kind: Some(code_action_kind(assist.id.1)), | 854 | kind: Some(code_action_kind(assist.id.1)), |
857 | edit: None, | 855 | edit: None, |
858 | is_preferred: None, | 856 | is_preferred: None, |
859 | data: Some(lsp_ext::CodeActionData { | ||
860 | id: format!("{}:{}", assist.id.0, index.to_string()), | ||
861 | code_action_params, | ||
862 | }), | ||
863 | }; | ||
864 | Ok(res) | ||
865 | } | ||
866 | |||
867 | pub(crate) fn resolved_code_action( | ||
868 | snap: &GlobalStateSnapshot, | ||
869 | assist: Assist, | ||
870 | ) -> Result<lsp_ext::CodeAction> { | ||
871 | let change = assist.source_change.unwrap(); | ||
872 | let res = lsp_ext::CodeAction { | ||
873 | edit: Some(snippet_workspace_edit(snap, change)?), | ||
874 | title: assist.label.to_string(), | ||
875 | group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0), | ||
876 | kind: Some(code_action_kind(assist.id.1)), | ||
877 | is_preferred: None, | ||
878 | data: None, | 857 | data: None, |
879 | }; | 858 | }; |
859 | match (assist.source_change, resolve_data) { | ||
860 | (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?), | ||
861 | (None, Some((index, code_action_params))) => { | ||
862 | res.data = Some(lsp_ext::CodeActionData { | ||
863 | id: format!("{}:{}", assist.id.0, index.to_string()), | ||
864 | code_action_params, | ||
865 | }); | ||
866 | } | ||
867 | (None, None) => { | ||
868 | stdx::never!("assist should always be resolved if client can't do lazy resolving") | ||
869 | } | ||
870 | }; | ||
880 | Ok(res) | 871 | Ok(res) |
881 | } | 872 | } |
882 | 873 | ||
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs index 1e4c04bbf..52a2674d5 100644 --- a/crates/rust-analyzer/tests/rust-analyzer/main.rs +++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs | |||
@@ -340,7 +340,6 @@ fn main() {} | |||
340 | } | 340 | } |
341 | ] | 341 | ] |
342 | }, | 342 | }, |
343 | "isPreferred": false, | ||
344 | "kind": "quickfix", | 343 | "kind": "quickfix", |
345 | "title": "Create module" | 344 | "title": "Create module" |
346 | }]), | 345 | }]), |
@@ -411,7 +410,6 @@ fn main() {{}} | |||
411 | } | 410 | } |
412 | ] | 411 | ] |
413 | }, | 412 | }, |
414 | "isPreferred": false, | ||
415 | "kind": "quickfix", | 413 | "kind": "quickfix", |
416 | "title": "Create module" | 414 | "title": "Create module" |
417 | }]), | 415 | }]), |
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs index 5e388c0f0..75e677762 100644 --- a/crates/rust-analyzer/tests/rust-analyzer/support.rs +++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs | |||
@@ -168,6 +168,7 @@ impl Server { | |||
168 | self.send_notification(r) | 168 | self.send_notification(r) |
169 | } | 169 | } |
170 | 170 | ||
171 | #[track_caller] | ||
171 | pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value) | 172 | pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value) |
172 | where | 173 | where |
173 | R: lsp_types::request::Request, | 174 | R: lsp_types::request::Request, |
diff --git a/crates/test_utils/src/assert_linear.rs b/crates/test_utils/src/assert_linear.rs new file mode 100644 index 000000000..6ecc232e1 --- /dev/null +++ b/crates/test_utils/src/assert_linear.rs | |||
@@ -0,0 +1,112 @@ | |||
1 | //! Checks that a set of measurements looks like a linear function rather than | ||
2 | //! like a quadratic function. Algorithm: | ||
3 | //! | ||
4 | //! 1. Linearly scale input to be in [0; 1) | ||
5 | //! 2. Using linear regression, compute the best linear function approximating | ||
6 | //! the input. | ||
7 | //! 3. Compute RMSE and maximal absolute error. | ||
8 | //! 4. Check that errors are within tolerances and that the constant term is not | ||
9 | //! too negative. | ||
10 | //! | ||
11 | //! Ideally, we should use a proper "model selection" to directly compare | ||
12 | //! quadratic and linear models, but that sounds rather complicated: | ||
13 | //! | ||
14 | //! https://stats.stackexchange.com/questions/21844/selecting-best-model-based-on-linear-quadratic-and-cubic-fit-of-data | ||
15 | //! | ||
16 | //! We might get false positives on a VM, but never false negatives. So, if the | ||
17 | //! first round fails, we repeat the ordeal three more times and fail only if | ||
18 | //! every time there's a fault. | ||
19 | use stdx::format_to; | ||
20 | |||
21 | #[derive(Default)] | ||
22 | pub struct AssertLinear { | ||
23 | rounds: Vec<Round>, | ||
24 | } | ||
25 | |||
26 | #[derive(Default)] | ||
27 | struct Round { | ||
28 | samples: Vec<(f64, f64)>, | ||
29 | plot: String, | ||
30 | linear: bool, | ||
31 | } | ||
32 | |||
33 | impl AssertLinear { | ||
34 | pub fn next_round(&mut self) -> bool { | ||
35 | if let Some(round) = self.rounds.last_mut() { | ||
36 | round.finish(); | ||
37 | } | ||
38 | if self.rounds.iter().any(|it| it.linear) || self.rounds.len() == 4 { | ||
39 | return false; | ||
40 | } | ||
41 | self.rounds.push(Round::default()); | ||
42 | true | ||
43 | } | ||
44 | |||
45 | pub fn sample(&mut self, x: f64, y: f64) { | ||
46 | self.rounds.last_mut().unwrap().samples.push((x, y)) | ||
47 | } | ||
48 | } | ||
49 | |||
50 | impl Drop for AssertLinear { | ||
51 | fn drop(&mut self) { | ||
52 | assert!(!self.rounds.is_empty()); | ||
53 | if self.rounds.iter().all(|it| !it.linear) { | ||
54 | for round in &self.rounds { | ||
55 | eprintln!("\n{}", round.plot); | ||
56 | } | ||
57 | panic!("Doesn't look linear!") | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | impl Round { | ||
63 | fn finish(&mut self) { | ||
64 | let (mut xs, mut ys): (Vec<_>, Vec<_>) = self.samples.iter().copied().unzip(); | ||
65 | normalize(&mut xs); | ||
66 | normalize(&mut ys); | ||
67 | let xy = xs.iter().copied().zip(ys.iter().copied()); | ||
68 | |||
69 | // Linear regression: finding a and b to fit y = a + b*x. | ||
70 | |||
71 | let mean_x = mean(&xs); | ||
72 | let mean_y = mean(&ys); | ||
73 | |||
74 | let b = { | ||
75 | let mut num = 0.0; | ||
76 | let mut denom = 0.0; | ||
77 | for (x, y) in xy.clone() { | ||
78 | num += (x - mean_x) * (y - mean_y); | ||
79 | denom += (x - mean_x).powi(2); | ||
80 | } | ||
81 | num / denom | ||
82 | }; | ||
83 | |||
84 | let a = mean_y - b * mean_x; | ||
85 | |||
86 | self.plot = format!("y_pred = {:.3} + {:.3} * x\n\nx y y_pred\n", a, b); | ||
87 | |||
88 | let mut se = 0.0; | ||
89 | let mut max_error = 0.0f64; | ||
90 | for (x, y) in xy { | ||
91 | let y_pred = a + b * x; | ||
92 | se += (y - y_pred).powi(2); | ||
93 | max_error = max_error.max((y_pred - y).abs()); | ||
94 | |||
95 | format_to!(self.plot, "{:.3} {:.3} {:.3}\n", x, y, y_pred); | ||
96 | } | ||
97 | |||
98 | let rmse = (se / xs.len() as f64).sqrt(); | ||
99 | format_to!(self.plot, "\nrmse = {:.3} max error = {:.3}", rmse, max_error); | ||
100 | |||
101 | self.linear = rmse < 0.05 && max_error < 0.1 && a > -0.1; | ||
102 | |||
103 | fn normalize(xs: &mut Vec<f64>) { | ||
104 | let max = xs.iter().copied().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap(); | ||
105 | xs.iter_mut().for_each(|it| *it /= max); | ||
106 | } | ||
107 | |||
108 | fn mean(xs: &[f64]) -> f64 { | ||
109 | xs.iter().copied().sum::<f64>() / (xs.len() as f64) | ||
110 | } | ||
111 | } | ||
112 | } | ||
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index c5f859790..72466c957 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -8,6 +8,7 @@ | |||
8 | 8 | ||
9 | pub mod bench_fixture; | 9 | pub mod bench_fixture; |
10 | mod fixture; | 10 | mod fixture; |
11 | mod assert_linear; | ||
11 | 12 | ||
12 | use std::{ | 13 | use std::{ |
13 | convert::{TryFrom, TryInto}, | 14 | convert::{TryFrom, TryInto}, |
@@ -22,7 +23,7 @@ use text_size::{TextRange, TextSize}; | |||
22 | pub use dissimilar::diff as __diff; | 23 | pub use dissimilar::diff as __diff; |
23 | pub use rustc_hash::FxHashMap; | 24 | pub use rustc_hash::FxHashMap; |
24 | 25 | ||
25 | pub use crate::fixture::Fixture; | 26 | pub use crate::{assert_linear::AssertLinear, fixture::Fixture}; |
26 | 27 | ||
27 | pub const CURSOR_MARKER: &str = "$0"; | 28 | pub const CURSOR_MARKER: &str = "$0"; |
28 | pub const ESCAPED_CURSOR_MARKER: &str = "\\$0"; | 29 | pub const ESCAPED_CURSOR_MARKER: &str = "\\$0"; |