@@ -34,22 +34,14 @@ fn diagnostic_severity(
34
34
Some ( res)
35
35
}
36
36
37
- /// Check whether a file name is from macro invocation
38
- fn is_from_macro ( file_name : & str ) -> bool {
37
+ /// Checks whether a file name is from macro invocation and does not refer to an actual file.
38
+ fn is_dummy_macro_file ( file_name : & str ) -> bool {
39
+ // FIXME: current rustc does not seem to emit `<macro file>` files anymore?
39
40
file_name. starts_with ( '<' ) && file_name. ends_with ( '>' )
40
41
}
41
42
42
- /// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary
43
- fn location ( workspace_root : & Path , span : & DiagnosticSpan ) -> lsp_types:: Location {
44
- let mut span = span. clone ( ) ;
45
- while let Some ( expansion) = span. expansion {
46
- span = expansion. span ;
47
- }
48
- return location_naive ( workspace_root, & span) ;
49
- }
50
-
51
43
/// Converts a Rust span to a LSP location
52
- fn location_naive ( workspace_root : & Path , span : & DiagnosticSpan ) -> lsp_types:: Location {
44
+ fn location ( workspace_root : & Path , span : & DiagnosticSpan ) -> lsp_types:: Location {
53
45
let file_name = workspace_root. join ( & span. file_name ) ;
54
46
let uri = url_from_abs_path ( & file_name) ;
55
47
@@ -62,7 +54,25 @@ fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Lo
62
54
lsp_types:: Location { uri, range }
63
55
}
64
56
65
- /// Converts a secondary Rust span to a LSP related inflocation(ormation
57
+ /// Extracts a suitable "primary" location from a rustc diagnostic.
58
+ ///
59
+ /// This takes locations pointing into the standard library, or generally outside the current
60
+ /// workspace into account and tries to avoid those, in case macros are involved.
61
+ fn primary_location ( workspace_root : & Path , span : & DiagnosticSpan ) -> lsp_types:: Location {
62
+ let span_stack = std:: iter:: successors ( Some ( span) , |span| Some ( & span. expansion . as_ref ( ) ?. span ) ) ;
63
+ for span in span_stack. clone ( ) {
64
+ let abs_path = workspace_root. join ( & span. file_name ) ;
65
+ if !is_dummy_macro_file ( & span. file_name ) && abs_path. starts_with ( workspace_root) {
66
+ return location ( workspace_root, span) ;
67
+ }
68
+ }
69
+
70
+ // Fall back to the outermost macro invocation if no suitable span comes up.
71
+ let last_span = span_stack. last ( ) . unwrap ( ) ;
72
+ location ( workspace_root, last_span)
73
+ }
74
+
75
+ /// Converts a secondary Rust span to a LSP related information
66
76
///
67
77
/// If the span is unlabelled this will return `None`.
68
78
fn diagnostic_related_information (
@@ -231,7 +241,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
231
241
primary_spans
232
242
. iter ( )
233
243
. flat_map ( |primary_span| {
234
- let location = location ( workspace_root, & primary_span) ;
244
+ let primary_location = primary_location ( workspace_root, & primary_span) ;
235
245
236
246
let mut message = message. clone ( ) ;
237
247
if needs_primary_span_label {
@@ -243,31 +253,47 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
243
253
// Each primary diagnostic span may result in multiple LSP diagnostics.
244
254
let mut diagnostics = Vec :: new ( ) ;
245
255
246
- let mut related_macro_info = None ;
256
+ let mut related_info_macro_calls = vec ! [ ] ;
247
257
248
258
// If error occurs from macro expansion, add related info pointing to
249
259
// where the error originated
250
260
// Also, we would generate an additional diagnostic, so that exact place of macro
251
261
// will be highlighted in the error origin place.
252
- if !is_from_macro ( & primary_span. file_name ) && primary_span. expansion . is_some ( ) {
253
- let in_macro_location = location_naive ( workspace_root, & primary_span) ;
262
+ let span_stack = std:: iter:: successors ( Some ( * primary_span) , |span| {
263
+ Some ( & span. expansion . as_ref ( ) ?. span )
264
+ } ) ;
265
+ for ( i, span) in span_stack. enumerate ( ) {
266
+ if is_dummy_macro_file ( & span. file_name ) {
267
+ continue ;
268
+ }
254
269
255
- // Add related information for the main disagnostic.
256
- related_macro_info = Some ( lsp_types:: DiagnosticRelatedInformation {
257
- location : in_macro_location. clone ( ) ,
258
- message : "Error originated from macro here" . to_string ( ) ,
259
- } ) ;
270
+ // First span is the original diagnostic, others are macro call locations that
271
+ // generated that code.
272
+ let is_in_macro_call = i != 0 ;
260
273
274
+ let secondary_location = location ( workspace_root, & span) ;
275
+ if secondary_location == primary_location {
276
+ continue ;
277
+ }
278
+ related_info_macro_calls. push ( lsp_types:: DiagnosticRelatedInformation {
279
+ location : secondary_location. clone ( ) ,
280
+ message : if is_in_macro_call {
281
+ "Error originated from macro call here" . to_string ( )
282
+ } else {
283
+ "Actual error occurred here" . to_string ( )
284
+ } ,
285
+ } ) ;
261
286
// For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
262
287
let information_for_additional_diagnostic =
263
288
vec ! [ lsp_types:: DiagnosticRelatedInformation {
264
- location: location . clone( ) ,
289
+ location: primary_location . clone( ) ,
265
290
message: "Exact error occurred here" . to_string( ) ,
266
291
} ] ;
267
292
268
293
let diagnostic = lsp_types:: Diagnostic {
269
- range : in_macro_location. range ,
270
- severity,
294
+ range : secondary_location. range ,
295
+ // downgrade to hint if we're pointing at the macro
296
+ severity : Some ( lsp_types:: DiagnosticSeverity :: Hint ) ,
271
297
code : code. clone ( ) . map ( lsp_types:: NumberOrString :: String ) ,
272
298
code_description : code_description. clone ( ) ,
273
299
source : Some ( source. clone ( ) ) ,
@@ -276,33 +302,34 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
276
302
tags : if tags. is_empty ( ) { None } else { Some ( tags. clone ( ) ) } ,
277
303
data : None ,
278
304
} ;
279
-
280
305
diagnostics. push ( MappedRustDiagnostic {
281
- url : in_macro_location . uri ,
306
+ url : secondary_location . uri ,
282
307
diagnostic,
283
308
fixes : Vec :: new ( ) ,
284
309
} ) ;
285
310
}
286
311
287
312
// Emit the primary diagnostic.
288
313
diagnostics. push ( MappedRustDiagnostic {
289
- url : location . uri . clone ( ) ,
314
+ url : primary_location . uri . clone ( ) ,
290
315
diagnostic : lsp_types:: Diagnostic {
291
- range : location . range ,
316
+ range : primary_location . range ,
292
317
severity,
293
318
code : code. clone ( ) . map ( lsp_types:: NumberOrString :: String ) ,
294
319
code_description : code_description. clone ( ) ,
295
320
source : Some ( source. clone ( ) ) ,
296
321
message,
297
- related_information : if subdiagnostics. is_empty ( ) {
298
- None
299
- } else {
300
- let mut related = subdiagnostics
322
+ related_information : {
323
+ let info = related_info_macro_calls
301
324
. iter ( )
302
- . map ( |sub| sub. related . clone ( ) )
325
+ . cloned ( )
326
+ . chain ( subdiagnostics. iter ( ) . map ( |sub| sub. related . clone ( ) ) )
303
327
. collect :: < Vec < _ > > ( ) ;
304
- related. extend ( related_macro_info) ;
305
- Some ( related)
328
+ if info. is_empty ( ) {
329
+ None
330
+ } else {
331
+ Some ( info)
332
+ }
306
333
} ,
307
334
tags : if tags. is_empty ( ) { None } else { Some ( tags. clone ( ) ) } ,
308
335
data : None ,
@@ -314,7 +341,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
314
341
// This is useful because they will show up in the user's editor, unlike
315
342
// `related_information`, which just produces hard-to-read links, at least in VS Code.
316
343
let back_ref = lsp_types:: DiagnosticRelatedInformation {
317
- location,
344
+ location : primary_location ,
318
345
message : "original diagnostic" . to_string ( ) ,
319
346
} ;
320
347
for sub in & subdiagnostics {
0 commit comments