10902: Handle multiple cargo check quick fix spans r=Veykril a=brandondong

Resolves https://github.com/rust-analyzer/rust-analyzer/issues/10705.

**Cause:**
- For a cargo check diagnostic with multiple spans, only a single quick fix action would be created at the location of `spans[0]`. Additionally, the hover window details would only show the location of `spans[0]` next to the message.

**Fix:**
- Allow cargo check quick fix actions to be triggerable from multiple selection ranges. Specifically, if the selection intersects with any of the replacement spans, the quick fix action is shown.
- No change in behavior for the hover window details. It's pretty minor and I think showing multiple locations next to the message may be more confusing anyways.

Co-authored-by: Brandon <brandondong604@hotmail.com>
This commit is contained in:
bors[bot] 2021-12-05 10:52:54 +00:00 committed by GitHub
commit 8a084e6aca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 384 additions and 295 deletions

View file

@ -29,7 +29,8 @@ pub(crate) struct DiagnosticCollection {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Fix { pub(crate) struct Fix {
pub(crate) range: lsp_types::Range, // Fixes may be triggerable from multiple ranges.
pub(crate) ranges: Vec<lsp_types::Range>,
pub(crate) action: lsp_ext::CodeAction, pub(crate) action: lsp_ext::CodeAction,
} }
@ -43,7 +44,7 @@ impl DiagnosticCollection {
&mut self, &mut self,
file_id: FileId, file_id: FileId,
diagnostic: lsp_types::Diagnostic, diagnostic: lsp_types::Diagnostic,
fixes: Vec<lsp_ext::CodeAction>, fix: Option<Fix>,
) { ) {
let diagnostics = self.check.entry(file_id).or_default(); let diagnostics = self.check.entry(file_id).or_default();
for existing_diagnostic in diagnostics.iter() { for existing_diagnostic in diagnostics.iter() {
@ -53,10 +54,7 @@ impl DiagnosticCollection {
} }
let check_fixes = Arc::make_mut(&mut self.check_fixes); let check_fixes = Arc::make_mut(&mut self.check_fixes);
check_fixes check_fixes.entry(file_id).or_default().extend(fix);
.entry(file_id)
.or_default()
.extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action }));
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
self.changes.insert(file_id); self.changes.insert(file_id);
} }

View file

@ -114,7 +114,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -205,7 +205,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -296,55 +296,69 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [ fix: Some(
CodeAction { Fix {
title: "consider passing by value instead: `self`", ranges: [
group: None, Range {
kind: Some( start: Position {
CodeActionKind( line: 41,
"quickfix", character: 23,
), },
), end: Position {
edit: Some( line: 41,
SnippetWorkspaceEdit { character: 28,
changes: Some( },
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/compiler/mir/tagset.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 41,
character: 23,
},
end: Position {
line: 41,
character: 28,
},
},
new_text: "self",
},
],
},
),
document_changes: None,
change_annotations: None,
}, },
), ],
is_preferred: Some( action: CodeAction {
true, title: "consider passing by value instead: `self`",
), group: None,
data: None, kind: Some(
CodeActionKind(
"quickfix",
),
),
edit: Some(
SnippetWorkspaceEdit {
changes: Some(
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/compiler/mir/tagset.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 41,
character: 23,
},
end: Position {
line: 41,
character: 28,
},
},
new_text: "self",
},
],
},
),
document_changes: None,
change_annotations: None,
},
),
is_preferred: Some(
true,
),
data: None,
},
}, },
], ),
}, },
] ]

View file

@ -59,6 +59,6 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
] ]

View file

@ -64,7 +64,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -131,7 +131,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -224,6 +224,6 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
] ]

View file

@ -59,6 +59,6 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
] ]

View file

@ -59,6 +59,6 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
] ]

View file

@ -72,7 +72,7 @@
), ),
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -143,55 +143,69 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [ fix: Some(
CodeAction { Fix {
title: "consider prefixing with an underscore: `_foo`", ranges: [
group: None, Range {
kind: Some( start: Position {
CodeActionKind( line: 290,
"quickfix", character: 8,
), },
), end: Position {
edit: Some( line: 290,
SnippetWorkspaceEdit { character: 11,
changes: Some( },
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/driver/subcommand/repl.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 290,
character: 8,
},
end: Position {
line: 290,
character: 11,
},
},
new_text: "_foo",
},
],
},
),
document_changes: None,
change_annotations: None,
}, },
), ],
is_preferred: Some( action: CodeAction {
true, title: "consider prefixing with an underscore: `_foo`",
), group: None,
data: None, kind: Some(
CodeActionKind(
"quickfix",
),
),
edit: Some(
SnippetWorkspaceEdit {
changes: Some(
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/driver/subcommand/repl.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 290,
character: 8,
},
end: Position {
line: 290,
character: 11,
},
},
new_text: "_foo",
},
],
},
),
document_changes: None,
change_annotations: None,
},
),
is_preferred: Some(
true,
),
data: None,
},
}, },
], ),
}, },
] ]

View file

@ -72,7 +72,7 @@
), ),
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -143,55 +143,69 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [ fix: Some(
CodeAction { Fix {
title: "consider prefixing with an underscore: `_foo`", ranges: [
group: None, Range {
kind: Some( start: Position {
CodeActionKind( line: 290,
"quickfix", character: 8,
), },
), end: Position {
edit: Some( line: 290,
SnippetWorkspaceEdit { character: 11,
changes: Some( },
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/driver/subcommand/repl.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 290,
character: 8,
},
end: Position {
line: 290,
character: 11,
},
},
new_text: "_foo",
},
],
},
),
document_changes: None,
change_annotations: None,
}, },
), ],
is_preferred: Some( action: CodeAction {
true, title: "consider prefixing with an underscore: `_foo`",
), group: None,
data: None, kind: Some(
CodeActionKind(
"quickfix",
),
),
edit: Some(
SnippetWorkspaceEdit {
changes: Some(
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/driver/subcommand/repl.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 290,
character: 8,
},
end: Position {
line: 290,
character: 11,
},
},
new_text: "_foo",
},
],
},
),
document_changes: None,
change_annotations: None,
},
),
is_preferred: Some(
true,
),
data: None,
},
}, },
], ),
}, },
] ]

View file

@ -72,7 +72,7 @@
), ),
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -143,55 +143,69 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [ fix: Some(
CodeAction { Fix {
title: "consider prefixing with an underscore: `_foo`", ranges: [
group: None, Range {
kind: Some( start: Position {
CodeActionKind( line: 290,
"quickfix", character: 8,
), },
), end: Position {
edit: Some( line: 290,
SnippetWorkspaceEdit { character: 11,
changes: Some( },
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/driver/subcommand/repl.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 290,
character: 8,
},
end: Position {
line: 290,
character: 11,
},
},
new_text: "_foo",
},
],
},
),
document_changes: None,
change_annotations: None,
}, },
), ],
is_preferred: Some( action: CodeAction {
true, title: "consider prefixing with an underscore: `_foo`",
), group: None,
data: None, kind: Some(
CodeActionKind(
"quickfix",
),
),
edit: Some(
SnippetWorkspaceEdit {
changes: Some(
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/driver/subcommand/repl.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 290,
character: 8,
},
end: Position {
line: 290,
character: 11,
},
},
new_text: "_foo",
},
],
},
),
document_changes: None,
change_annotations: None,
},
),
is_preferred: Some(
true,
),
data: None,
},
}, },
], ),
}, },
] ]

View file

@ -88,7 +88,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -179,6 +179,6 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
] ]

View file

@ -114,7 +114,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -205,7 +205,7 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [], fix: None,
}, },
MappedRustDiagnostic { MappedRustDiagnostic {
url: Url { url: Url {
@ -296,68 +296,92 @@
tags: None, tags: None,
data: None, data: None,
}, },
fixes: [ fix: Some(
CodeAction { Fix {
title: "return the expression directly: `(0..10).collect()`", ranges: [
group: None, Range {
kind: Some( start: Position {
CodeActionKind( line: 2,
"quickfix", character: 4,
), },
), end: Position {
edit: Some( line: 2,
SnippetWorkspaceEdit { character: 30,
changes: Some( },
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/src/main.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 2,
character: 4,
},
end: Position {
line: 2,
character: 30,
},
},
new_text: "",
},
TextEdit {
range: Range {
start: Position {
line: 3,
character: 4,
},
end: Position {
line: 3,
character: 5,
},
},
new_text: "(0..10).collect()",
},
],
},
),
document_changes: None,
change_annotations: None,
}, },
), Range {
is_preferred: Some( start: Position {
true, line: 3,
), character: 4,
data: None, },
end: Position {
line: 3,
character: 5,
},
},
],
action: CodeAction {
title: "return the expression directly: `(0..10).collect()`",
group: None,
kind: Some(
CodeActionKind(
"quickfix",
),
),
edit: Some(
SnippetWorkspaceEdit {
changes: Some(
{
Url {
scheme: "file",
cannot_be_a_base: false,
username: "",
password: None,
host: None,
port: None,
path: "/test/src/main.rs",
query: None,
fragment: None,
}: [
TextEdit {
range: Range {
start: Position {
line: 2,
character: 4,
},
end: Position {
line: 2,
character: 30,
},
},
new_text: "",
},
TextEdit {
range: Range {
start: Position {
line: 3,
character: 4,
},
end: Position {
line: 3,
character: 5,
},
},
new_text: "(0..10).collect()",
},
],
},
),
document_changes: None,
change_annotations: None,
},
),
is_preferred: Some(
true,
),
data: None,
},
}, },
], ),
}, },
] ]

View file

@ -9,7 +9,7 @@ use vfs::{AbsPath, AbsPathBuf};
use crate::{lsp_ext, to_proto::url_from_abs_path}; use crate::{lsp_ext, to_proto::url_from_abs_path};
use super::DiagnosticsMapConfig; use super::{DiagnosticsMapConfig, Fix};
/// Determines the LSP severity from a diagnostic /// Determines the LSP severity from a diagnostic
fn diagnostic_severity( fn diagnostic_severity(
@ -124,7 +124,7 @@ fn resolve_path(
struct SubDiagnostic { struct SubDiagnostic {
related: lsp_types::DiagnosticRelatedInformation, related: lsp_types::DiagnosticRelatedInformation,
suggested_fix: Option<lsp_ext::CodeAction>, suggested_fix: Option<Fix>,
} }
enum MappedRustChildDiagnostic { enum MappedRustChildDiagnostic {
@ -181,18 +181,24 @@ fn map_rust_child_diagnostic(
location: location(config, workspace_root, spans[0]), location: location(config, workspace_root, spans[0]),
message: message.clone(), message: message.clone(),
}, },
suggested_fix: Some(lsp_ext::CodeAction { suggested_fix: Some(Fix {
title: message, ranges: spans
group: None, .iter()
kind: Some(lsp_types::CodeActionKind::QUICKFIX), .map(|&span| location(config, workspace_root, span).range)
edit: Some(lsp_ext::SnippetWorkspaceEdit { .collect(),
// FIXME: there's no good reason to use edit_map here.... action: lsp_ext::CodeAction {
changes: Some(edit_map), title: message,
document_changes: None, group: None,
change_annotations: None, kind: Some(lsp_types::CodeActionKind::QUICKFIX),
}), edit: Some(lsp_ext::SnippetWorkspaceEdit {
is_preferred: Some(true), // FIXME: there's no good reason to use edit_map here....
data: None, changes: Some(edit_map),
document_changes: None,
change_annotations: None,
}),
is_preferred: Some(true),
data: None,
},
}), }),
}) })
} }
@ -202,7 +208,7 @@ fn map_rust_child_diagnostic(
pub(crate) struct MappedRustDiagnostic { pub(crate) struct MappedRustDiagnostic {
pub(crate) url: lsp_types::Url, pub(crate) url: lsp_types::Url,
pub(crate) diagnostic: lsp_types::Diagnostic, pub(crate) diagnostic: lsp_types::Diagnostic,
pub(crate) fixes: Vec<lsp_ext::CodeAction>, pub(crate) fix: Option<Fix>,
} }
/// Converts a Rust root diagnostic to LSP form /// Converts a Rust root diagnostic to LSP form
@ -359,7 +365,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
diagnostics.push(MappedRustDiagnostic { diagnostics.push(MappedRustDiagnostic {
url: secondary_location.uri, url: secondary_location.uri,
diagnostic, diagnostic,
fixes: Vec::new(), fix: None,
}); });
} }
@ -388,7 +394,7 @@ 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,
}, },
fixes: Vec::new(), fix: None,
}); });
// Emit hint-level diagnostics for all `related_information` entries such as "help"s. // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
@ -405,7 +411,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
} }
diagnostics.push(MappedRustDiagnostic { diagnostics.push(MappedRustDiagnostic {
url: sub.related.location.uri.clone(), url: sub.related.location.uri.clone(),
fixes: sub.suggested_fix.iter().cloned().collect(), fix: sub.suggested_fix.clone(),
diagnostic: lsp_types::Diagnostic { diagnostic: lsp_types::Diagnostic {
range: sub.related.location.range, range: sub.related.location.range,
severity: Some(lsp_types::DiagnosticSeverity::HINT), severity: Some(lsp_types::DiagnosticSeverity::HINT),

View file

@ -1081,8 +1081,13 @@ pub(crate) fn handle_code_action(
for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() { for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() {
// FIXME: this mapping is awkward and shouldn't exist. Refactor // FIXME: this mapping is awkward and shouldn't exist. Refactor
// `snap.check_fixes` to not convert to LSP prematurely. // `snap.check_fixes` to not convert to LSP prematurely.
let fix_range = from_proto::text_range(&line_index, fix.range); let intersect_fix_range = fix
if fix_range.intersect(frange.range).is_some() { .ranges
.iter()
.copied()
.map(|range| from_proto::text_range(&line_index, range))
.any(|fix_range| fix_range.intersect(frange.range).is_some());
if intersect_fix_range {
res.push(fix.action.clone()); res.push(fix.action.clone());
} }
} }

View file

@ -370,7 +370,7 @@ impl GlobalState {
Ok(file_id) => self.diagnostics.add_check_diagnostic( Ok(file_id) => self.diagnostics.add_check_diagnostic(
file_id, file_id,
diag.diagnostic, diag.diagnostic,
diag.fixes, diag.fix,
), ),
Err(err) => { Err(err) => {
tracing::error!( tracing::error!(