@@ -34,22 +34,14 @@ fn diagnostic_severity(
3434 Some ( res)
3535}
3636
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?
3940 file_name. starts_with ( '<' ) && file_name. ends_with ( '>' )
4041}
4142
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-
5143/// 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 {
5345 let file_name = workspace_root. join ( & span. file_name ) ;
5446 let uri = url_from_abs_path ( & file_name) ;
5547
@@ -62,7 +54,25 @@ fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Lo
6254 lsp_types:: Location { uri, range }
6355}
6456
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
6676///
6777/// If the span is unlabelled this will return `None`.
6878fn diagnostic_related_information (
@@ -231,7 +241,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
231241 primary_spans
232242 . iter ( )
233243 . flat_map ( |primary_span| {
234- let location = location ( workspace_root, & primary_span) ;
244+ let primary_location = primary_location ( workspace_root, & primary_span) ;
235245
236246 let mut message = message. clone ( ) ;
237247 if needs_primary_span_label {
@@ -243,31 +253,47 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
243253 // Each primary diagnostic span may result in multiple LSP diagnostics.
244254 let mut diagnostics = Vec :: new ( ) ;
245255
246- let mut related_macro_info = None ;
256+ let mut related_info_macro_calls = vec ! [ ] ;
247257
248258 // If error occurs from macro expansion, add related info pointing to
249259 // where the error originated
250260 // Also, we would generate an additional diagnostic, so that exact place of macro
251261 // 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+ }
254269
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 ;
260273
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+ } ) ;
261286 // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
262287 let information_for_additional_diagnostic =
263288 vec ! [ lsp_types:: DiagnosticRelatedInformation {
264- location: location . clone( ) ,
289+ location: primary_location . clone( ) ,
265290 message: "Exact error occurred here" . to_string( ) ,
266291 } ] ;
267292
268293 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 ) ,
271297 code : code. clone ( ) . map ( lsp_types:: NumberOrString :: String ) ,
272298 code_description : code_description. clone ( ) ,
273299 source : Some ( source. clone ( ) ) ,
@@ -276,33 +302,34 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
276302 tags : if tags. is_empty ( ) { None } else { Some ( tags. clone ( ) ) } ,
277303 data : None ,
278304 } ;
279-
280305 diagnostics. push ( MappedRustDiagnostic {
281- url : in_macro_location . uri ,
306+ url : secondary_location . uri ,
282307 diagnostic,
283308 fixes : Vec :: new ( ) ,
284309 } ) ;
285310 }
286311
287312 // Emit the primary diagnostic.
288313 diagnostics. push ( MappedRustDiagnostic {
289- url : location . uri . clone ( ) ,
314+ url : primary_location . uri . clone ( ) ,
290315 diagnostic : lsp_types:: Diagnostic {
291- range : location . range ,
316+ range : primary_location . range ,
292317 severity,
293318 code : code. clone ( ) . map ( lsp_types:: NumberOrString :: String ) ,
294319 code_description : code_description. clone ( ) ,
295320 source : Some ( source. clone ( ) ) ,
296321 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
301324 . iter ( )
302- . map ( |sub| sub. related . clone ( ) )
325+ . cloned ( )
326+ . chain ( subdiagnostics. iter ( ) . map ( |sub| sub. related . clone ( ) ) )
303327 . collect :: < Vec < _ > > ( ) ;
304- related. extend ( related_macro_info) ;
305- Some ( related)
328+ if info. is_empty ( ) {
329+ None
330+ } else {
331+ Some ( info)
332+ }
306333 } ,
307334 tags : if tags. is_empty ( ) { None } else { Some ( tags. clone ( ) ) } ,
308335 data : None ,
@@ -314,7 +341,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
314341 // This is useful because they will show up in the user's editor, unlike
315342 // `related_information`, which just produces hard-to-read links, at least in VS Code.
316343 let back_ref = lsp_types:: DiagnosticRelatedInformation {
317- location,
344+ location : primary_location ,
318345 message : "original diagnostic" . to_string ( ) ,
319346 } ;
320347 for sub in & subdiagnostics {
0 commit comments