mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 07:04:49 +00:00
Merge #8265
8265: Improve rustc diagnostic mapping r=jonas-schievink a=jonas-schievink Try to mirror rustc diagnostics more closely by: * Emitting hint-level diagnostics at *all* macro invocation sites that caused the diagnostic * Previously we emitted a copy of the diagnostic (not at hint level) at the last macro invocation site only * Emitting the original diagnostic inside the macro, if it was caused by a macro * Always including related information pointing to the invocation site or the macro, respectively (the old code contained a bug that would sometimes omit it) Fixes https://github.com/rust-analyzer/rust-analyzer/issues/8260   Co-authored-by: Jonas Schievink <jonasschievink@gmail.com> Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
This commit is contained in:
commit
ea8feca31a
2 changed files with 220 additions and 75 deletions
|
@ -1,4 +1,134 @@
|
||||||
[
|
[
|
||||||
|
MappedRustDiagnostic {
|
||||||
|
url: Url {
|
||||||
|
scheme: "file",
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/test/crates/hir_def/src/path.rs",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
range: Range {
|
||||||
|
start: Position {
|
||||||
|
line: 271,
|
||||||
|
character: 8,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: 271,
|
||||||
|
character: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
severity: Some(
|
||||||
|
Hint,
|
||||||
|
),
|
||||||
|
code: None,
|
||||||
|
code_description: None,
|
||||||
|
source: Some(
|
||||||
|
"rustc",
|
||||||
|
),
|
||||||
|
message: "Please register your known path in the path module",
|
||||||
|
related_information: Some(
|
||||||
|
[
|
||||||
|
DiagnosticRelatedInformation {
|
||||||
|
location: Location {
|
||||||
|
uri: Url {
|
||||||
|
scheme: "file",
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/test/crates/hir_def/src/path.rs",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
range: Range {
|
||||||
|
start: Position {
|
||||||
|
line: 264,
|
||||||
|
character: 8,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: 264,
|
||||||
|
character: 76,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
message: "Exact error occurred here",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
tags: None,
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
fixes: [],
|
||||||
|
},
|
||||||
|
MappedRustDiagnostic {
|
||||||
|
url: Url {
|
||||||
|
scheme: "file",
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/test/crates/hir_def/src/data.rs",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
range: Range {
|
||||||
|
start: Position {
|
||||||
|
line: 79,
|
||||||
|
character: 15,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: 79,
|
||||||
|
character: 41,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
severity: Some(
|
||||||
|
Hint,
|
||||||
|
),
|
||||||
|
code: None,
|
||||||
|
code_description: None,
|
||||||
|
source: Some(
|
||||||
|
"rustc",
|
||||||
|
),
|
||||||
|
message: "Please register your known path in the path module",
|
||||||
|
related_information: Some(
|
||||||
|
[
|
||||||
|
DiagnosticRelatedInformation {
|
||||||
|
location: Location {
|
||||||
|
uri: Url {
|
||||||
|
scheme: "file",
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/test/crates/hir_def/src/path.rs",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
range: Range {
|
||||||
|
start: Position {
|
||||||
|
line: 264,
|
||||||
|
character: 8,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: 264,
|
||||||
|
character: 76,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
message: "Exact error occurred here",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
tags: None,
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
fixes: [],
|
||||||
|
},
|
||||||
MappedRustDiagnostic {
|
MappedRustDiagnostic {
|
||||||
url: Url {
|
url: Url {
|
||||||
scheme: "file",
|
scheme: "file",
|
||||||
|
@ -40,32 +170,26 @@
|
||||||
password: None,
|
password: None,
|
||||||
host: None,
|
host: None,
|
||||||
port: None,
|
port: None,
|
||||||
path: "/test/crates/hir_def/src/data.rs",
|
path: "/test/crates/hir_def/src/path.rs",
|
||||||
query: None,
|
query: None,
|
||||||
fragment: None,
|
fragment: None,
|
||||||
},
|
},
|
||||||
range: Range {
|
range: Range {
|
||||||
start: Position {
|
start: Position {
|
||||||
line: 79,
|
line: 271,
|
||||||
character: 15,
|
character: 8,
|
||||||
},
|
},
|
||||||
end: Position {
|
end: Position {
|
||||||
line: 79,
|
line: 271,
|
||||||
character: 41,
|
character: 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
message: "Exact error occurred here",
|
message: "Error originated from macro call here",
|
||||||
},
|
},
|
||||||
],
|
DiagnosticRelatedInformation {
|
||||||
),
|
location: Location {
|
||||||
tags: None,
|
uri: Url {
|
||||||
data: None,
|
|
||||||
},
|
|
||||||
fixes: [],
|
|
||||||
},
|
|
||||||
MappedRustDiagnostic {
|
|
||||||
url: Url {
|
|
||||||
scheme: "file",
|
scheme: "file",
|
||||||
username: "",
|
username: "",
|
||||||
password: None,
|
password: None,
|
||||||
|
@ -75,7 +199,6 @@
|
||||||
query: None,
|
query: None,
|
||||||
fragment: None,
|
fragment: None,
|
||||||
},
|
},
|
||||||
diagnostic: Diagnostic {
|
|
||||||
range: Range {
|
range: Range {
|
||||||
start: Position {
|
start: Position {
|
||||||
line: 79,
|
line: 79,
|
||||||
|
@ -86,16 +209,11 @@
|
||||||
character: 41,
|
character: 41,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
severity: Some(
|
},
|
||||||
Error,
|
message: "Error originated from macro call here",
|
||||||
|
},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
code: None,
|
|
||||||
code_description: None,
|
|
||||||
source: Some(
|
|
||||||
"rustc",
|
|
||||||
),
|
|
||||||
message: "Please register your known path in the path module",
|
|
||||||
related_information: None,
|
|
||||||
tags: None,
|
tags: None,
|
||||||
data: None,
|
data: None,
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,22 +34,14 @@ fn diagnostic_severity(
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether a file name is from macro invocation
|
/// Checks whether a file name is from macro invocation and does not refer to an actual file.
|
||||||
fn is_from_macro(file_name: &str) -> bool {
|
fn is_dummy_macro_file(file_name: &str) -> bool {
|
||||||
|
// FIXME: current rustc does not seem to emit `<macro file>` files anymore?
|
||||||
file_name.starts_with('<') && file_name.ends_with('>')
|
file_name.starts_with('<') && file_name.ends_with('>')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary
|
|
||||||
fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
|
|
||||||
let mut span = span.clone();
|
|
||||||
while let Some(expansion) = span.expansion {
|
|
||||||
span = expansion.span;
|
|
||||||
}
|
|
||||||
return location_naive(workspace_root, &span);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Rust span to a LSP location
|
/// Converts a Rust span to a LSP location
|
||||||
fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
|
fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
|
||||||
let file_name = workspace_root.join(&span.file_name);
|
let file_name = workspace_root.join(&span.file_name);
|
||||||
let uri = url_from_abs_path(&file_name);
|
let uri = url_from_abs_path(&file_name);
|
||||||
|
|
||||||
|
@ -62,7 +54,25 @@ fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Lo
|
||||||
lsp_types::Location { uri, range }
|
lsp_types::Location { uri, range }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a secondary Rust span to a LSP related inflocation(ormation
|
/// Extracts a suitable "primary" location from a rustc diagnostic.
|
||||||
|
///
|
||||||
|
/// This takes locations pointing into the standard library, or generally outside the current
|
||||||
|
/// workspace into account and tries to avoid those, in case macros are involved.
|
||||||
|
fn primary_location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location {
|
||||||
|
let span_stack = std::iter::successors(Some(span), |span| Some(&span.expansion.as_ref()?.span));
|
||||||
|
for span in span_stack.clone() {
|
||||||
|
let abs_path = workspace_root.join(&span.file_name);
|
||||||
|
if !is_dummy_macro_file(&span.file_name) && abs_path.starts_with(workspace_root) {
|
||||||
|
return location(workspace_root, span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the outermost macro invocation if no suitable span comes up.
|
||||||
|
let last_span = span_stack.last().unwrap();
|
||||||
|
location(workspace_root, last_span)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a secondary Rust span to a LSP related information
|
||||||
///
|
///
|
||||||
/// If the span is unlabelled this will return `None`.
|
/// If the span is unlabelled this will return `None`.
|
||||||
fn diagnostic_related_information(
|
fn diagnostic_related_information(
|
||||||
|
@ -231,7 +241,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
|
||||||
primary_spans
|
primary_spans
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|primary_span| {
|
.flat_map(|primary_span| {
|
||||||
let location = location(workspace_root, &primary_span);
|
let primary_location = primary_location(workspace_root, &primary_span);
|
||||||
|
|
||||||
let mut message = message.clone();
|
let mut message = message.clone();
|
||||||
if needs_primary_span_label {
|
if needs_primary_span_label {
|
||||||
|
@ -243,31 +253,47 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
|
||||||
// Each primary diagnostic span may result in multiple LSP diagnostics.
|
// Each primary diagnostic span may result in multiple LSP diagnostics.
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
|
|
||||||
let mut related_macro_info = None;
|
let mut related_info_macro_calls = vec![];
|
||||||
|
|
||||||
// If error occurs from macro expansion, add related info pointing to
|
// If error occurs from macro expansion, add related info pointing to
|
||||||
// where the error originated
|
// where the error originated
|
||||||
// Also, we would generate an additional diagnostic, so that exact place of macro
|
// Also, we would generate an additional diagnostic, so that exact place of macro
|
||||||
// will be highlighted in the error origin place.
|
// will be highlighted in the error origin place.
|
||||||
if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
|
let span_stack = std::iter::successors(Some(*primary_span), |span| {
|
||||||
let in_macro_location = location_naive(workspace_root, &primary_span);
|
Some(&span.expansion.as_ref()?.span)
|
||||||
|
|
||||||
// Add related information for the main disagnostic.
|
|
||||||
related_macro_info = Some(lsp_types::DiagnosticRelatedInformation {
|
|
||||||
location: in_macro_location.clone(),
|
|
||||||
message: "Error originated from macro here".to_string(),
|
|
||||||
});
|
});
|
||||||
|
for (i, span) in span_stack.enumerate() {
|
||||||
|
if is_dummy_macro_file(&span.file_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First span is the original diagnostic, others are macro call locations that
|
||||||
|
// generated that code.
|
||||||
|
let is_in_macro_call = i != 0;
|
||||||
|
|
||||||
|
let secondary_location = location(workspace_root, &span);
|
||||||
|
if secondary_location == primary_location {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
related_info_macro_calls.push(lsp_types::DiagnosticRelatedInformation {
|
||||||
|
location: secondary_location.clone(),
|
||||||
|
message: if is_in_macro_call {
|
||||||
|
"Error originated from macro call here".to_string()
|
||||||
|
} else {
|
||||||
|
"Actual error occurred here".to_string()
|
||||||
|
},
|
||||||
|
});
|
||||||
// For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
|
// For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
|
||||||
let information_for_additional_diagnostic =
|
let information_for_additional_diagnostic =
|
||||||
vec![lsp_types::DiagnosticRelatedInformation {
|
vec![lsp_types::DiagnosticRelatedInformation {
|
||||||
location: location.clone(),
|
location: primary_location.clone(),
|
||||||
message: "Exact error occurred here".to_string(),
|
message: "Exact error occurred here".to_string(),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let diagnostic = lsp_types::Diagnostic {
|
let diagnostic = lsp_types::Diagnostic {
|
||||||
range: in_macro_location.range,
|
range: secondary_location.range,
|
||||||
severity,
|
// downgrade to hint if we're pointing at the macro
|
||||||
|
severity: Some(lsp_types::DiagnosticSeverity::Hint),
|
||||||
code: code.clone().map(lsp_types::NumberOrString::String),
|
code: code.clone().map(lsp_types::NumberOrString::String),
|
||||||
code_description: code_description.clone(),
|
code_description: code_description.clone(),
|
||||||
source: Some(source.clone()),
|
source: Some(source.clone()),
|
||||||
|
@ -276,9 +302,8 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
|
||||||
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
|
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
|
||||||
data: None,
|
data: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
diagnostics.push(MappedRustDiagnostic {
|
diagnostics.push(MappedRustDiagnostic {
|
||||||
url: in_macro_location.uri,
|
url: secondary_location.uri,
|
||||||
diagnostic,
|
diagnostic,
|
||||||
fixes: Vec::new(),
|
fixes: Vec::new(),
|
||||||
});
|
});
|
||||||
|
@ -286,23 +311,25 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
|
||||||
|
|
||||||
// Emit the primary diagnostic.
|
// Emit the primary diagnostic.
|
||||||
diagnostics.push(MappedRustDiagnostic {
|
diagnostics.push(MappedRustDiagnostic {
|
||||||
url: location.uri.clone(),
|
url: primary_location.uri.clone(),
|
||||||
diagnostic: lsp_types::Diagnostic {
|
diagnostic: lsp_types::Diagnostic {
|
||||||
range: location.range,
|
range: primary_location.range,
|
||||||
severity,
|
severity,
|
||||||
code: code.clone().map(lsp_types::NumberOrString::String),
|
code: code.clone().map(lsp_types::NumberOrString::String),
|
||||||
code_description: code_description.clone(),
|
code_description: code_description.clone(),
|
||||||
source: Some(source.clone()),
|
source: Some(source.clone()),
|
||||||
message,
|
message,
|
||||||
related_information: if subdiagnostics.is_empty() {
|
related_information: {
|
||||||
|
let info = related_info_macro_calls
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(subdiagnostics.iter().map(|sub| sub.related.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if info.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let mut related = subdiagnostics
|
Some(info)
|
||||||
.iter()
|
}
|
||||||
.map(|sub| sub.related.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
related.extend(related_macro_info);
|
|
||||||
Some(related)
|
|
||||||
},
|
},
|
||||||
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
|
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
|
||||||
data: None,
|
data: None,
|
||||||
|
@ -314,7 +341,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
|
||||||
// This is useful because they will show up in the user's editor, unlike
|
// This is useful because they will show up in the user's editor, unlike
|
||||||
// `related_information`, which just produces hard-to-read links, at least in VS Code.
|
// `related_information`, which just produces hard-to-read links, at least in VS Code.
|
||||||
let back_ref = lsp_types::DiagnosticRelatedInformation {
|
let back_ref = lsp_types::DiagnosticRelatedInformation {
|
||||||
location,
|
location: primary_location,
|
||||||
message: "original diagnostic".to_string(),
|
message: "original diagnostic".to_string(),
|
||||||
};
|
};
|
||||||
for sub in &subdiagnostics {
|
for sub in &subdiagnostics {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue