aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-12-06 13:26:54 +0000
committerGitHub <[email protected]>2020-12-06 13:26:54 +0000
commit1403ddf029a6a4ced904146ac7b475c923f6129f (patch)
tree5de8d09fea220460bf49657f086d3130c79eb491
parent8d5aa08712e782f22f04525f291ec74dae183568 (diff)
parent45b8b3d57fd1a38a0ccce8516cbebbd09184d59a (diff)
Merge #6734
6734: Emit additional diagnostics for hints/help/etc r=lnicola a=jonas-schievink This makes rust-analyzer diagnostics match native rustc diagnostics in the terminal more closely. Unfortunately all of this is a bodge, since we already provide this information to the client in the form of `DiagnosticRelatedInformation`, but at least VS Code chooses such a poor UI for these that they don't help much, as evidenced [here](https://twitter.com/yaahc_/status/1335297260444250112) and in https://github.com/rust-lang/rust/issues/79741. This PR papers over these client UI problems by taking the `DiagnosticRelatedInformation` and turning each one into its own hint-level diagnostic, which makes it show up in the source code. Quick fixes are attached to all resulting diagnostics, which makes them more discoverable. ### Example: "Consider removing this semicolon" ![screenshot-2020-12-06-01:27:29](https://user-images.githubusercontent.com/1786438/101268366-46423980-3762-11eb-9a69-1ff0b1806c2f.png) ![screenshot-2020-12-06-01:27:39](https://user-images.githubusercontent.com/1786438/101268367-46dad000-3762-11eb-81fa-afd234d44f17.png) ![screenshot-2020-12-06-01:27:46](https://user-images.githubusercontent.com/1786438/101268368-46dad000-3762-11eb-9205-4b9bd9f4406d.png) ### Example: "Value used after move" ![screenshot-2020-12-06-01:33:00](https://user-images.githubusercontent.com/1786438/101268447-22332800-3763-11eb-85ce-8c742927a2c8.png) ![screenshot-2020-12-06-01:33:07](https://user-images.githubusercontent.com/1786438/101268448-22cbbe80-3763-11eb-8f16-0590895d8bc6.png) Co-authored-by: Jonas Schievink <[email protected]>
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt164
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt60
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt138
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt82
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt303
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs189
8 files changed, 1093 insertions, 119 deletions
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
index 72f6c5725..7576097b3 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
@@ -104,4 +104,168 @@
104 }, 104 },
105 fixes: [], 105 fixes: [],
106 }, 106 },
107 MappedRustDiagnostic {
108 url: Url {
109 scheme: "file",
110 host: None,
111 port: None,
112 path: "/test/compiler/lib.rs",
113 query: None,
114 fragment: None,
115 },
116 diagnostic: Diagnostic {
117 range: Range {
118 start: Position {
119 line: 0,
120 character: 8,
121 },
122 end: Position {
123 line: 0,
124 character: 19,
125 },
126 },
127 severity: Some(
128 Hint,
129 ),
130 code: Some(
131 String(
132 "trivially_copy_pass_by_ref",
133 ),
134 ),
135 code_description: Some(
136 CodeDescription {
137 href: Url {
138 scheme: "https",
139 host: Some(
140 Domain(
141 "rust-lang.github.io",
142 ),
143 ),
144 port: None,
145 path: "/rust-clippy/master/index.html",
146 query: None,
147 fragment: Some(
148 "trivially_copy_pass_by_ref",
149 ),
150 },
151 },
152 ),
153 source: Some(
154 "clippy",
155 ),
156 message: "lint level defined here",
157 related_information: Some(
158 [
159 DiagnosticRelatedInformation {
160 location: Location {
161 uri: Url {
162 scheme: "file",
163 host: None,
164 port: None,
165 path: "/test/compiler/mir/tagset.rs",
166 query: None,
167 fragment: None,
168 },
169 range: Range {
170 start: Position {
171 line: 41,
172 character: 23,
173 },
174 end: Position {
175 line: 41,
176 character: 28,
177 },
178 },
179 },
180 message: "original diagnostic",
181 },
182 ],
183 ),
184 tags: None,
185 data: None,
186 },
187 fixes: [],
188 },
189 MappedRustDiagnostic {
190 url: Url {
191 scheme: "file",
192 host: None,
193 port: None,
194 path: "/test/compiler/mir/tagset.rs",
195 query: None,
196 fragment: None,
197 },
198 diagnostic: Diagnostic {
199 range: Range {
200 start: Position {
201 line: 41,
202 character: 23,
203 },
204 end: Position {
205 line: 41,
206 character: 28,
207 },
208 },
209 severity: Some(
210 Hint,
211 ),
212 code: Some(
213 String(
214 "trivially_copy_pass_by_ref",
215 ),
216 ),
217 code_description: Some(
218 CodeDescription {
219 href: Url {
220 scheme: "https",
221 host: Some(
222 Domain(
223 "rust-lang.github.io",
224 ),
225 ),
226 port: None,
227 path: "/rust-clippy/master/index.html",
228 query: None,
229 fragment: Some(
230 "trivially_copy_pass_by_ref",
231 ),
232 },
233 },
234 ),
235 source: Some(
236 "clippy",
237 ),
238 message: "consider passing by value instead",
239 related_information: Some(
240 [
241 DiagnosticRelatedInformation {
242 location: Location {
243 uri: Url {
244 scheme: "file",
245 host: None,
246 port: None,
247 path: "/test/compiler/mir/tagset.rs",
248 query: None,
249 fragment: None,
250 },
251 range: Range {
252 start: Position {
253 line: 41,
254 character: 23,
255 },
256 end: Position {
257 line: 41,
258 character: 28,
259 },
260 },
261 },
262 message: "original diagnostic",
263 },
264 ],
265 ),
266 tags: None,
267 data: None,
268 },
269 fixes: [],
270 },
107] 271]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index bbec6a796..bdcf2a38f 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -4,19 +4,19 @@
4 scheme: "file", 4 scheme: "file",
5 host: None, 5 host: None,
6 port: None, 6 port: None,
7 path: "/test/crates/hir_def/src/data.rs", 7 path: "/test/crates/hir_def/src/path.rs",
8 query: None, 8 query: None,
9 fragment: None, 9 fragment: None,
10 }, 10 },
11 diagnostic: Diagnostic { 11 diagnostic: Diagnostic {
12 range: Range { 12 range: Range {
13 start: Position { 13 start: Position {
14 line: 79, 14 line: 264,
15 character: 15, 15 character: 8,
16 }, 16 },
17 end: Position { 17 end: Position {
18 line: 79, 18 line: 264,
19 character: 41, 19 character: 76,
20 }, 20 },
21 }, 21 },
22 severity: Some( 22 severity: Some(
@@ -36,22 +36,22 @@
36 scheme: "file", 36 scheme: "file",
37 host: None, 37 host: None,
38 port: None, 38 port: None,
39 path: "/test/crates/hir_def/src/path.rs", 39 path: "/test/crates/hir_def/src/data.rs",
40 query: None, 40 query: None,
41 fragment: None, 41 fragment: None,
42 }, 42 },
43 range: Range { 43 range: Range {
44 start: Position { 44 start: Position {
45 line: 264, 45 line: 79,
46 character: 8, 46 character: 15,
47 }, 47 },
48 end: Position { 48 end: Position {
49 line: 264, 49 line: 79,
50 character: 76, 50 character: 41,
51 }, 51 },
52 }, 52 },
53 }, 53 },
54 message: "Error originated from macro here", 54 message: "Exact error occurred here",
55 }, 55 },
56 ], 56 ],
57 ), 57 ),
@@ -65,19 +65,19 @@
65 scheme: "file", 65 scheme: "file",
66 host: None, 66 host: None,
67 port: None, 67 port: None,
68 path: "/test/crates/hir_def/src/path.rs", 68 path: "/test/crates/hir_def/src/data.rs",
69 query: None, 69 query: None,
70 fragment: None, 70 fragment: None,
71 }, 71 },
72 diagnostic: Diagnostic { 72 diagnostic: Diagnostic {
73 range: Range { 73 range: Range {
74 start: Position { 74 start: Position {
75 line: 264, 75 line: 79,
76 character: 8, 76 character: 15,
77 }, 77 },
78 end: Position { 78 end: Position {
79 line: 264, 79 line: 79,
80 character: 76, 80 character: 41,
81 }, 81 },
82 }, 82 },
83 severity: Some( 83 severity: Some(
@@ -89,33 +89,7 @@
89 "rustc", 89 "rustc",
90 ), 90 ),
91 message: "Please register your known path in the path module", 91 message: "Please register your known path in the path module",
92 related_information: Some( 92 related_information: None,
93 [
94 DiagnosticRelatedInformation {
95 location: Location {
96 uri: Url {
97 scheme: "file",
98 host: None,
99 port: None,
100 path: "/test/crates/hir_def/src/data.rs",
101 query: None,
102 fragment: None,
103 },
104 range: Range {
105 start: Position {
106 line: 79,
107 character: 15,
108 },
109 end: Position {
110 line: 79,
111 character: 41,
112 },
113 },
114 },
115 message: "Exact error occured here",
116 },
117 ],
118 ),
119 tags: None, 93 tags: None,
120 data: None, 94 data: None,
121 }, 95 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
index c709de95f..23d42b4d0 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
@@ -32,7 +32,33 @@
32 "rustc", 32 "rustc",
33 ), 33 ),
34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", 34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
35 related_information: None, 35 related_information: Some(
36 [
37 DiagnosticRelatedInformation {
38 location: Location {
39 uri: Url {
40 scheme: "file",
41 host: None,
42 port: None,
43 path: "/test/driver/subcommand/repl.rs",
44 query: None,
45 fragment: None,
46 },
47 range: Range {
48 start: Position {
49 line: 290,
50 character: 8,
51 },
52 end: Position {
53 line: 290,
54 character: 11,
55 },
56 },
57 },
58 message: "consider prefixing with an underscore",
59 },
60 ],
61 ),
36 tags: Some( 62 tags: Some(
37 [ 63 [
38 Unnecessary, 64 Unnecessary,
@@ -87,4 +113,114 @@
87 }, 113 },
88 ], 114 ],
89 }, 115 },
116 MappedRustDiagnostic {
117 url: Url {
118 scheme: "file",
119 host: None,
120 port: None,
121 path: "/test/driver/subcommand/repl.rs",
122 query: None,
123 fragment: None,
124 },
125 diagnostic: Diagnostic {
126 range: Range {
127 start: Position {
128 line: 290,
129 character: 8,
130 },
131 end: Position {
132 line: 290,
133 character: 11,
134 },
135 },
136 severity: Some(
137 Hint,
138 ),
139 code: Some(
140 String(
141 "unused_variables",
142 ),
143 ),
144 code_description: None,
145 source: Some(
146 "rustc",
147 ),
148 message: "consider prefixing with an underscore",
149 related_information: Some(
150 [
151 DiagnosticRelatedInformation {
152 location: Location {
153 uri: Url {
154 scheme: "file",
155 host: None,
156 port: None,
157 path: "/test/driver/subcommand/repl.rs",
158 query: None,
159 fragment: None,
160 },
161 range: Range {
162 start: Position {
163 line: 290,
164 character: 8,
165 },
166 end: Position {
167 line: 290,
168 character: 11,
169 },
170 },
171 },
172 message: "original diagnostic",
173 },
174 ],
175 ),
176 tags: None,
177 data: None,
178 },
179 fixes: [
180 CodeAction {
181 title: "consider prefixing with an underscore",
182 group: None,
183 kind: Some(
184 CodeActionKind(
185 "quickfix",
186 ),
187 ),
188 edit: Some(
189 SnippetWorkspaceEdit {
190 changes: Some(
191 {
192 Url {
193 scheme: "file",
194 host: None,
195 port: None,
196 path: "/test/driver/subcommand/repl.rs",
197 query: None,
198 fragment: None,
199 }: [
200 TextEdit {
201 range: Range {
202 start: Position {
203 line: 290,
204 character: 8,
205 },
206 end: Position {
207 line: 290,
208 character: 11,
209 },
210 },
211 new_text: "_foo",
212 },
213 ],
214 },
215 ),
216 document_changes: None,
217 },
218 ),
219 is_preferred: Some(
220 true,
221 ),
222 data: None,
223 },
224 ],
225 },
90] 226]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
index 632f438d7..4e428bedc 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
@@ -32,7 +32,33 @@
32 "rustc", 32 "rustc",
33 ), 33 ),
34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", 34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
35 related_information: None, 35 related_information: Some(
36 [
37 DiagnosticRelatedInformation {
38 location: Location {
39 uri: Url {
40 scheme: "file",
41 host: None,
42 port: None,
43 path: "/test/driver/subcommand/repl.rs",
44 query: None,
45 fragment: None,
46 },
47 range: Range {
48 start: Position {
49 line: 290,
50 character: 8,
51 },
52 end: Position {
53 line: 290,
54 character: 11,
55 },
56 },
57 },
58 message: "consider prefixing with an underscore",
59 },
60 ],
61 ),
36 tags: Some( 62 tags: Some(
37 [ 63 [
38 Unnecessary, 64 Unnecessary,
@@ -87,4 +113,114 @@
87 }, 113 },
88 ], 114 ],
89 }, 115 },
116 MappedRustDiagnostic {
117 url: Url {
118 scheme: "file",
119 host: None,
120 port: None,
121 path: "/test/driver/subcommand/repl.rs",
122 query: None,
123 fragment: None,
124 },
125 diagnostic: Diagnostic {
126 range: Range {
127 start: Position {
128 line: 290,
129 character: 8,
130 },
131 end: Position {
132 line: 290,
133 character: 11,
134 },
135 },
136 severity: Some(
137 Hint,
138 ),
139 code: Some(
140 String(
141 "unused_variables",
142 ),
143 ),
144 code_description: None,
145 source: Some(
146 "rustc",
147 ),
148 message: "consider prefixing with an underscore",
149 related_information: Some(
150 [
151 DiagnosticRelatedInformation {
152 location: Location {
153 uri: Url {
154 scheme: "file",
155 host: None,
156 port: None,
157 path: "/test/driver/subcommand/repl.rs",
158 query: None,
159 fragment: None,
160 },
161 range: Range {
162 start: Position {
163 line: 290,
164 character: 8,
165 },
166 end: Position {
167 line: 290,
168 character: 11,
169 },
170 },
171 },
172 message: "original diagnostic",
173 },
174 ],
175 ),
176 tags: None,
177 data: None,
178 },
179 fixes: [
180 CodeAction {
181 title: "consider prefixing with an underscore",
182 group: None,
183 kind: Some(
184 CodeActionKind(
185 "quickfix",
186 ),
187 ),
188 edit: Some(
189 SnippetWorkspaceEdit {
190 changes: Some(
191 {
192 Url {
193 scheme: "file",
194 host: None,
195 port: None,
196 path: "/test/driver/subcommand/repl.rs",
197 query: None,
198 fragment: None,
199 }: [
200 TextEdit {
201 range: Range {
202 start: Position {
203 line: 290,
204 character: 8,
205 },
206 end: Position {
207 line: 290,
208 character: 11,
209 },
210 },
211 new_text: "_foo",
212 },
213 ],
214 },
215 ),
216 document_changes: None,
217 },
218 ),
219 is_preferred: Some(
220 true,
221 ),
222 data: None,
223 },
224 ],
225 },
90] 226]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
index c0b79428d..4ddd7efae 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
@@ -32,7 +32,33 @@
32 "rustc", 32 "rustc",
33 ), 33 ),
34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", 34 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
35 related_information: None, 35 related_information: Some(
36 [
37 DiagnosticRelatedInformation {
38 location: Location {
39 uri: Url {
40 scheme: "file",
41 host: None,
42 port: None,
43 path: "/test/driver/subcommand/repl.rs",
44 query: None,
45 fragment: None,
46 },
47 range: Range {
48 start: Position {
49 line: 290,
50 character: 8,
51 },
52 end: Position {
53 line: 290,
54 character: 11,
55 },
56 },
57 },
58 message: "consider prefixing with an underscore",
59 },
60 ],
61 ),
36 tags: Some( 62 tags: Some(
37 [ 63 [
38 Unnecessary, 64 Unnecessary,
@@ -87,4 +113,114 @@
87 }, 113 },
88 ], 114 ],
89 }, 115 },
116 MappedRustDiagnostic {
117 url: Url {
118 scheme: "file",
119 host: None,
120 port: None,
121 path: "/test/driver/subcommand/repl.rs",
122 query: None,
123 fragment: None,
124 },
125 diagnostic: Diagnostic {
126 range: Range {
127 start: Position {
128 line: 290,
129 character: 8,
130 },
131 end: Position {
132 line: 290,
133 character: 11,
134 },
135 },
136 severity: Some(
137 Hint,
138 ),
139 code: Some(
140 String(
141 "unused_variables",
142 ),
143 ),
144 code_description: None,
145 source: Some(
146 "rustc",
147 ),
148 message: "consider prefixing with an underscore",
149 related_information: Some(
150 [
151 DiagnosticRelatedInformation {
152 location: Location {
153 uri: Url {
154 scheme: "file",
155 host: None,
156 port: None,
157 path: "/test/driver/subcommand/repl.rs",
158 query: None,
159 fragment: None,
160 },
161 range: Range {
162 start: Position {
163 line: 290,
164 character: 8,
165 },
166 end: Position {
167 line: 290,
168 character: 11,
169 },
170 },
171 },
172 message: "original diagnostic",
173 },
174 ],
175 ),
176 tags: None,
177 data: None,
178 },
179 fixes: [
180 CodeAction {
181 title: "consider prefixing with an underscore",
182 group: None,
183 kind: Some(
184 CodeActionKind(
185 "quickfix",
186 ),
187 ),
188 edit: Some(
189 SnippetWorkspaceEdit {
190 changes: Some(
191 {
192 Url {
193 scheme: "file",
194 host: None,
195 port: None,
196 path: "/test/driver/subcommand/repl.rs",
197 query: None,
198 fragment: None,
199 }: [
200 TextEdit {
201 range: Range {
202 start: Position {
203 line: 290,
204 character: 8,
205 },
206 end: Position {
207 line: 290,
208 character: 11,
209 },
210 },
211 new_text: "_foo",
212 },
213 ],
214 },
215 ),
216 document_changes: None,
217 },
218 ),
219 is_preferred: Some(
220 true,
221 ),
222 data: None,
223 },
224 ],
225 },
90] 226]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
index b9650f3e4..f455cf25e 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
@@ -81,4 +81,86 @@
81 }, 81 },
82 fixes: [], 82 fixes: [],
83 }, 83 },
84 MappedRustDiagnostic {
85 url: Url {
86 scheme: "file",
87 host: None,
88 port: None,
89 path: "/test/compiler/ty/select.rs",
90 query: None,
91 fragment: None,
92 },
93 diagnostic: Diagnostic {
94 range: Range {
95 start: Position {
96 line: 218,
97 character: 4,
98 },
99 end: Position {
100 line: 230,
101 character: 5,
102 },
103 },
104 severity: Some(
105 Hint,
106 ),
107 code: Some(
108 String(
109 "E0061",
110 ),
111 ),
112 code_description: Some(
113 CodeDescription {
114 href: Url {
115 scheme: "https",
116 host: Some(
117 Domain(
118 "doc.rust-lang.org",
119 ),
120 ),
121 port: None,
122 path: "/error-index.html",
123 query: None,
124 fragment: Some(
125 "E0061",
126 ),
127 },
128 },
129 ),
130 source: Some(
131 "rustc",
132 ),
133 message: "defined here",
134 related_information: Some(
135 [
136 DiagnosticRelatedInformation {
137 location: Location {
138 uri: Url {
139 scheme: "file",
140 host: None,
141 port: None,
142 path: "/test/compiler/ty/select.rs",
143 query: None,
144 fragment: None,
145 },
146 range: Range {
147 start: Position {
148 line: 103,
149 character: 17,
150 },
151 end: Position {
152 line: 103,
153 character: 29,
154 },
155 },
156 },
157 message: "original diagnostic",
158 },
159 ],
160 ),
161 tags: None,
162 data: None,
163 },
164 fixes: [],
165 },
84] 166]
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
index c45f68a91..4cbdb3b92 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
@@ -74,6 +74,309 @@
74 }, 74 },
75 message: "unnecessary let binding", 75 message: "unnecessary let binding",
76 }, 76 },
77 DiagnosticRelatedInformation {
78 location: Location {
79 uri: Url {
80 scheme: "file",
81 host: None,
82 port: None,
83 path: "/test/src/main.rs",
84 query: None,
85 fragment: None,
86 },
87 range: Range {
88 start: Position {
89 line: 2,
90 character: 4,
91 },
92 end: Position {
93 line: 2,
94 character: 30,
95 },
96 },
97 },
98 message: "return the expression directly",
99 },
100 ],
101 ),
102 tags: None,
103 data: None,
104 },
105 fixes: [
106 CodeAction {
107 title: "return the expression directly",
108 group: None,
109 kind: Some(
110 CodeActionKind(
111 "quickfix",
112 ),
113 ),
114 edit: Some(
115 SnippetWorkspaceEdit {
116 changes: Some(
117 {
118 Url {
119 scheme: "file",
120 host: None,
121 port: None,
122 path: "/test/src/main.rs",
123 query: None,
124 fragment: None,
125 }: [
126 TextEdit {
127 range: Range {
128 start: Position {
129 line: 2,
130 character: 4,
131 },
132 end: Position {
133 line: 2,
134 character: 30,
135 },
136 },
137 new_text: "",
138 },
139 TextEdit {
140 range: Range {
141 start: Position {
142 line: 3,
143 character: 4,
144 },
145 end: Position {
146 line: 3,
147 character: 5,
148 },
149 },
150 new_text: "(0..10).collect()",
151 },
152 ],
153 },
154 ),
155 document_changes: None,
156 },
157 ),
158 is_preferred: Some(
159 true,
160 ),
161 data: None,
162 },
163 ],
164 },
165 MappedRustDiagnostic {
166 url: Url {
167 scheme: "file",
168 host: None,
169 port: None,
170 path: "/test/src/main.rs",
171 query: None,
172 fragment: None,
173 },
174 diagnostic: Diagnostic {
175 range: Range {
176 start: Position {
177 line: 2,
178 character: 4,
179 },
180 end: Position {
181 line: 2,
182 character: 30,
183 },
184 },
185 severity: Some(
186 Hint,
187 ),
188 code: Some(
189 String(
190 "let_and_return",
191 ),
192 ),
193 code_description: Some(
194 CodeDescription {
195 href: Url {
196 scheme: "https",
197 host: Some(
198 Domain(
199 "rust-lang.github.io",
200 ),
201 ),
202 port: None,
203 path: "/rust-clippy/master/index.html",
204 query: None,
205 fragment: Some(
206 "let_and_return",
207 ),
208 },
209 },
210 ),
211 source: Some(
212 "clippy",
213 ),
214 message: "unnecessary let binding",
215 related_information: Some(
216 [
217 DiagnosticRelatedInformation {
218 location: Location {
219 uri: Url {
220 scheme: "file",
221 host: None,
222 port: None,
223 path: "/test/src/main.rs",
224 query: None,
225 fragment: None,
226 },
227 range: Range {
228 start: Position {
229 line: 3,
230 character: 4,
231 },
232 end: Position {
233 line: 3,
234 character: 5,
235 },
236 },
237 },
238 message: "original diagnostic",
239 },
240 ],
241 ),
242 tags: None,
243 data: None,
244 },
245 fixes: [
246 CodeAction {
247 title: "return the expression directly",
248 group: None,
249 kind: Some(
250 CodeActionKind(
251 "quickfix",
252 ),
253 ),
254 edit: Some(
255 SnippetWorkspaceEdit {
256 changes: Some(
257 {
258 Url {
259 scheme: "file",
260 host: None,
261 port: None,
262 path: "/test/src/main.rs",
263 query: None,
264 fragment: None,
265 }: [
266 TextEdit {
267 range: Range {
268 start: Position {
269 line: 2,
270 character: 4,
271 },
272 end: Position {
273 line: 2,
274 character: 30,
275 },
276 },
277 new_text: "",
278 },
279 TextEdit {
280 range: Range {
281 start: Position {
282 line: 3,
283 character: 4,
284 },
285 end: Position {
286 line: 3,
287 character: 5,
288 },
289 },
290 new_text: "(0..10).collect()",
291 },
292 ],
293 },
294 ),
295 document_changes: None,
296 },
297 ),
298 is_preferred: Some(
299 true,
300 ),
301 data: None,
302 },
303 ],
304 },
305 MappedRustDiagnostic {
306 url: Url {
307 scheme: "file",
308 host: None,
309 port: None,
310 path: "/test/src/main.rs",
311 query: None,
312 fragment: None,
313 },
314 diagnostic: Diagnostic {
315 range: Range {
316 start: Position {
317 line: 2,
318 character: 4,
319 },
320 end: Position {
321 line: 2,
322 character: 30,
323 },
324 },
325 severity: Some(
326 Hint,
327 ),
328 code: Some(
329 String(
330 "let_and_return",
331 ),
332 ),
333 code_description: Some(
334 CodeDescription {
335 href: Url {
336 scheme: "https",
337 host: Some(
338 Domain(
339 "rust-lang.github.io",
340 ),
341 ),
342 port: None,
343 path: "/rust-clippy/master/index.html",
344 query: None,
345 fragment: Some(
346 "let_and_return",
347 ),
348 },
349 },
350 ),
351 source: Some(
352 "clippy",
353 ),
354 message: "return the expression directly",
355 related_information: Some(
356 [
357 DiagnosticRelatedInformation {
358 location: Location {
359 uri: Url {
360 scheme: "file",
361 host: None,
362 port: None,
363 path: "/test/src/main.rs",
364 query: None,
365 fragment: None,
366 },
367 range: Range {
368 start: Position {
369 line: 3,
370 character: 4,
371 },
372 end: Position {
373 line: 3,
374 character: 5,
375 },
376 },
377 },
378 message: "original diagnostic",
379 },
77 ], 380 ],
78 ), 381 ),
79 tags: None, 382 tags: None,
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 324019614..f16f97131 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -75,8 +75,10 @@ fn diagnostic_related_information(
75} 75}
76 76
77enum MappedRustChildDiagnostic { 77enum MappedRustChildDiagnostic {
78 Related(lsp_types::DiagnosticRelatedInformation), 78 Related {
79 SuggestedFix(lsp_ext::CodeAction), 79 related: lsp_types::DiagnosticRelatedInformation,
80 suggested_fix: Option<lsp_ext::CodeAction>,
81 },
80 MessageLine(String), 82 MessageLine(String),
81} 83}
82 84
@@ -103,23 +105,32 @@ fn map_rust_child_diagnostic(
103 } 105 }
104 106
105 if edit_map.is_empty() { 107 if edit_map.is_empty() {
106 MappedRustChildDiagnostic::Related(lsp_types::DiagnosticRelatedInformation { 108 MappedRustChildDiagnostic::Related {
107 location: location(workspace_root, spans[0]), 109 related: lsp_types::DiagnosticRelatedInformation {
108 message: rd.message.clone(), 110 location: location(workspace_root, spans[0]),
109 }) 111 message: rd.message.clone(),
112 },
113 suggested_fix: None,
114 }
110 } else { 115 } else {
111 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { 116 MappedRustChildDiagnostic::Related {
112 title: rd.message.clone(), 117 related: lsp_types::DiagnosticRelatedInformation {
113 group: None, 118 location: location(workspace_root, spans[0]),
114 kind: Some(lsp_types::CodeActionKind::QUICKFIX), 119 message: rd.message.clone(),
115 edit: Some(lsp_ext::SnippetWorkspaceEdit { 120 },
116 // FIXME: there's no good reason to use edit_map here.... 121 suggested_fix: Some(lsp_ext::CodeAction {
117 changes: Some(edit_map), 122 title: rd.message.clone(),
118 document_changes: None, 123 group: None,
124 kind: Some(lsp_types::CodeActionKind::QUICKFIX),
125 edit: Some(lsp_ext::SnippetWorkspaceEdit {
126 // FIXME: there's no good reason to use edit_map here....
127 changes: Some(edit_map),
128 document_changes: None,
129 }),
130 is_preferred: Some(true),
131 data: None,
119 }), 132 }),
120 is_preferred: Some(true), 133 }
121 data: None,
122 })
123 } 134 }
124} 135}
125 136
@@ -179,8 +190,12 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
179 for child in &rd.children { 190 for child in &rd.children {
180 let child = map_rust_child_diagnostic(workspace_root, &child); 191 let child = map_rust_child_diagnostic(workspace_root, &child);
181 match child { 192 match child {
182 MappedRustChildDiagnostic::Related(related) => related_information.push(related), 193 MappedRustChildDiagnostic::Related { related, suggested_fix } => {
183 MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), 194 related_information.push(related);
195 if let Some(code_action) = suggested_fix {
196 fixes.push(code_action);
197 }
198 }
184 MappedRustChildDiagnostic::MessageLine(message_line) => { 199 MappedRustChildDiagnostic::MessageLine(message_line) => {
185 format_to!(message, "\n{}", message_line); 200 format_to!(message, "\n{}", message_line);
186 201
@@ -219,7 +234,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
219 234
220 primary_spans 235 primary_spans
221 .iter() 236 .iter()
222 .map(|primary_span| { 237 .flat_map(|primary_span| {
223 let location = location(workspace_root, &primary_span); 238 let location = location(workspace_root, &primary_span);
224 239
225 let mut message = message.clone(); 240 let mut message = message.clone();
@@ -229,72 +244,100 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
229 } 244 }
230 } 245 }
231 246
247 // Each primary diagnostic span may result in multiple LSP diagnostics.
248 let mut diagnostics = Vec::new();
249
250 let mut related_macro_info = None;
251
232 // If error occurs from macro expansion, add related info pointing to 252 // If error occurs from macro expansion, add related info pointing to
233 // where the error originated 253 // where the error originated
234 // Also, we would generate an additional diagnostic, so that exact place of macro 254 // Also, we would generate an additional diagnostic, so that exact place of macro
235 // will be highlighted in the error origin place. 255 // will be highlighted in the error origin place.
236 let additional_diagnostic = 256 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
237 if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { 257 let in_macro_location = location_naive(workspace_root, &primary_span);
238 let in_macro_location = location_naive(workspace_root, &primary_span);
239 258
240 // Add related information for the main disagnostic. 259 // Add related information for the main disagnostic.
241 related_information.push(lsp_types::DiagnosticRelatedInformation { 260 related_macro_info = Some(lsp_types::DiagnosticRelatedInformation {
242 location: in_macro_location.clone(), 261 location: in_macro_location.clone(),
243 message: "Error originated from macro here".to_string(), 262 message: "Error originated from macro here".to_string(),
244 }); 263 });
245 264
246 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. 265 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
247 let information_for_additional_diagnostic = 266 let information_for_additional_diagnostic =
248 vec![lsp_types::DiagnosticRelatedInformation { 267 vec![lsp_types::DiagnosticRelatedInformation {
249 location: location.clone(), 268 location: location.clone(),
250 message: "Exact error occured here".to_string(), 269 message: "Exact error occurred here".to_string(),
251 }]; 270 }];
252 271
253 let diagnostic = lsp_types::Diagnostic { 272 let diagnostic = lsp_types::Diagnostic {
254 range: in_macro_location.range, 273 range: in_macro_location.range,
255 severity, 274 severity,
256 code: code.clone().map(lsp_types::NumberOrString::String), 275 code: code.clone().map(lsp_types::NumberOrString::String),
257 code_description: code_description.clone(), 276 code_description: code_description.clone(),
258 source: Some(source.clone()), 277 source: Some(source.clone()),
259 message: message.clone(), 278 message: message.clone(),
260 related_information: Some(information_for_additional_diagnostic), 279 related_information: Some(information_for_additional_diagnostic),
261 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 280 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
262 data: None, 281 data: None,
263 };
264
265 Some(MappedRustDiagnostic {
266 url: in_macro_location.uri,
267 diagnostic,
268 fixes: fixes.clone(),
269 })
270 } else {
271 None
272 }; 282 };
273 283
274 let diagnostic = lsp_types::Diagnostic { 284 diagnostics.push(MappedRustDiagnostic {
275 range: location.range, 285 url: in_macro_location.uri,
276 severity, 286 diagnostic,
277 code: code.clone().map(lsp_types::NumberOrString::String), 287 fixes: fixes.clone(),
278 code_description: code_description.clone(), 288 });
279 source: Some(source.clone()), 289 }
280 message, 290
281 related_information: if related_information.is_empty() { 291 // Emit the primary diagnostic.
282 None 292 diagnostics.push(MappedRustDiagnostic {
283 } else { 293 url: location.uri.clone(),
284 Some(related_information.clone()) 294 diagnostic: lsp_types::Diagnostic {
295 range: location.range,
296 severity,
297 code: code.clone().map(lsp_types::NumberOrString::String),
298 code_description: code_description.clone(),
299 source: Some(source.clone()),
300 message,
301 related_information: if related_information.is_empty() {
302 None
303 } else {
304 let mut related = related_information.clone();
305 related.extend(related_macro_info);
306 Some(related)
307 },
308 tags: if tags.is_empty() { None } else { Some(tags.clone()) },
309 data: None,
285 }, 310 },
286 tags: if tags.is_empty() { None } else { Some(tags.clone()) }, 311 fixes: fixes.clone(),
287 data: None, 312 });
288 };
289 313
290 let main_diagnostic = 314 // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
291 MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() }; 315 // This is useful because they will show up in the user's editor, unlike
292 match additional_diagnostic { 316 // `related_information`, which just produces hard-to-read links, at least in VS Code.
293 None => vec![main_diagnostic], 317 let back_ref = lsp_types::DiagnosticRelatedInformation {
294 Some(additional_diagnostic) => vec![main_diagnostic, additional_diagnostic], 318 location,
319 message: "original diagnostic".to_string(),
320 };
321 for info in &related_information {
322 diagnostics.push(MappedRustDiagnostic {
323 url: info.location.uri.clone(),
324 fixes: fixes.clone(), // share fixes to make them easier to apply
325 diagnostic: lsp_types::Diagnostic {
326 range: info.location.range,
327 severity: Some(lsp_types::DiagnosticSeverity::Hint),
328 code: code.clone().map(lsp_types::NumberOrString::String),
329 code_description: code_description.clone(),
330 source: Some(source.clone()),
331 message: info.message.clone(),
332 related_information: Some(vec![back_ref.clone()]),
333 tags: None, // don't apply modifiers again
334 data: None,
335 },
336 });
295 } 337 }
338
339 diagnostics
296 }) 340 })
297 .flatten()
298 .collect() 341 .collect()
299} 342}
300 343