Skip to content

Commit 563d2d1

Browse files
bors[bot]matklad
andauthored
Merge #8481
8481: internal: prepare for lazy diagnostics r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
2 parents cae920a + 426d098 commit 563d2d1

File tree

6 files changed

+96
-77
lines changed

6 files changed

+96
-77
lines changed

crates/ide/src/diagnostics.rs

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use syntax::{
2525
use text_edit::TextEdit;
2626
use unlinked_file::UnlinkedFile;
2727

28-
use crate::{FileId, Label, SourceChange};
28+
use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
2929

3030
use self::fixes::DiagnosticWithFix;
3131

@@ -35,7 +35,7 @@ pub struct Diagnostic {
3535
pub message: String,
3636
pub range: TextRange,
3737
pub severity: Severity,
38-
pub fix: Option<Fix>,
38+
pub fix: Option<Assist>,
3939
pub unused: bool,
4040
pub code: Option<DiagnosticCode>,
4141
}
@@ -56,7 +56,7 @@ impl Diagnostic {
5656
}
5757
}
5858

59-
fn with_fix(self, fix: Option<Fix>) -> Self {
59+
fn with_fix(self, fix: Option<Assist>) -> Self {
6060
Self { fix, ..self }
6161
}
6262

@@ -69,21 +69,6 @@ impl Diagnostic {
6969
}
7070
}
7171

72-
#[derive(Debug)]
73-
pub struct Fix {
74-
pub label: Label,
75-
pub source_change: SourceChange,
76-
/// Allows to trigger the fix only when the caret is in the range given
77-
pub fix_trigger_range: TextRange,
78-
}
79-
80-
impl Fix {
81-
fn new(label: &str, source_change: SourceChange, fix_trigger_range: TextRange) -> Self {
82-
let label = Label::new(label);
83-
Self { label, source_change, fix_trigger_range }
84-
}
85-
}
86-
8772
#[derive(Debug, Copy, Clone)]
8873
pub enum Severity {
8974
Error,
@@ -261,7 +246,8 @@ fn check_unnecessary_braces_in_use_statement(
261246

262247
acc.push(
263248
Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
264-
.with_fix(Some(Fix::new(
249+
.with_fix(Some(fix(
250+
"remove_braces",
265251
"Remove unnecessary braces",
266252
SourceChange::from_text_edit(file_id, edit),
267253
use_range,
@@ -284,6 +270,17 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
284270
None
285271
}
286272

273+
fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
274+
assert!(!id.contains(' '));
275+
Assist {
276+
id: AssistId(id, AssistKind::QuickFix),
277+
label: Label::new(label),
278+
group: None,
279+
target,
280+
source_change: Some(source_change),
281+
}
282+
}
283+
287284
#[cfg(test)]
288285
mod tests {
289286
use expect_test::{expect, Expect};
@@ -308,20 +305,21 @@ mod tests {
308305
.unwrap();
309306
let fix = diagnostic.fix.unwrap();
310307
let actual = {
311-
let file_id = *fix.source_change.source_file_edits.keys().next().unwrap();
308+
let source_change = fix.source_change.unwrap();
309+
let file_id = *source_change.source_file_edits.keys().next().unwrap();
312310
let mut actual = analysis.file_text(file_id).unwrap().to_string();
313311

314-
for edit in fix.source_change.source_file_edits.values() {
312+
for edit in source_change.source_file_edits.values() {
315313
edit.apply(&mut actual);
316314
}
317315
actual
318316
};
319317

320318
assert_eq_text!(&after, &actual);
321319
assert!(
322-
fix.fix_trigger_range.contains_inclusive(file_position.offset),
320+
fix.target.contains_inclusive(file_position.offset),
323321
"diagnostic fix range {:?} does not touch cursor position {:?}",
324-
fix.fix_trigger_range,
322+
fix.target,
325323
file_position.offset
326324
);
327325
}
@@ -665,24 +663,31 @@ fn test_fn() {
665663
range: 0..8,
666664
severity: Error,
667665
fix: Some(
668-
Fix {
666+
Assist {
667+
id: AssistId(
668+
"create_module",
669+
QuickFix,
670+
),
669671
label: "Create module",
670-
source_change: SourceChange {
671-
source_file_edits: {},
672-
file_system_edits: [
673-
CreateFile {
674-
dst: AnchoredPathBuf {
675-
anchor: FileId(
676-
0,
677-
),
678-
path: "foo.rs",
672+
group: None,
673+
target: 0..8,
674+
source_change: Some(
675+
SourceChange {
676+
source_file_edits: {},
677+
file_system_edits: [
678+
CreateFile {
679+
dst: AnchoredPathBuf {
680+
anchor: FileId(
681+
0,
682+
),
683+
path: "foo.rs",
684+
},
685+
initial_contents: "",
679686
},
680-
initial_contents: "",
681-
},
682-
],
683-
is_snippet: false,
684-
},
685-
fix_trigger_range: 0..8,
687+
],
688+
is_snippet: false,
689+
},
690+
),
686691
},
687692
),
688693
unused: false,

crates/ide/src/diagnostics/field_shorthand.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange};
55
use syntax::{ast, match_ast, AstNode, SyntaxNode};
66
use text_edit::TextEdit;
77

8-
use crate::{Diagnostic, Fix};
8+
use crate::{diagnostics::fix, Diagnostic};
99

1010
pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
1111
match_ast! {
@@ -47,7 +47,8 @@ fn check_expr_field_shorthand(
4747
let field_range = record_field.syntax().text_range();
4848
acc.push(
4949
Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix(
50-
Some(Fix::new(
50+
Some(fix(
51+
"use_expr_field_shorthand",
5152
"Use struct shorthand initialization",
5253
SourceChange::from_text_edit(file_id, edit),
5354
field_range,
@@ -86,7 +87,8 @@ fn check_pat_field_shorthand(
8687

8788
let field_range = record_pat_field.syntax().text_range();
8889
acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix(
89-
Some(Fix::new(
90+
Some(fix(
91+
"use_pat_field_shorthand",
9092
"Use struct field shorthand",
9193
SourceChange::from_text_edit(file_id, edit),
9294
field_range,

crates/ide/src/diagnostics/fixes.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,21 @@ use syntax::{
2020
};
2121
use text_edit::TextEdit;
2222

23-
use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition};
23+
use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, FilePosition};
2424

2525
/// A [Diagnostic] that potentially has a fix available.
2626
///
2727
/// [Diagnostic]: hir::diagnostics::Diagnostic
2828
pub(crate) trait DiagnosticWithFix: Diagnostic {
29-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
29+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist>;
3030
}
3131

3232
impl DiagnosticWithFix for UnresolvedModule {
33-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
33+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
3434
let root = sema.db.parse_or_expand(self.file)?;
3535
let unresolved_module = self.decl.to_node(&root);
36-
Some(Fix::new(
36+
Some(fix(
37+
"create_module",
3738
"Create module",
3839
FileSystemEdit::CreateFile {
3940
dst: AnchoredPathBuf {
@@ -49,7 +50,7 @@ impl DiagnosticWithFix for UnresolvedModule {
4950
}
5051

5152
impl DiagnosticWithFix for NoSuchField {
52-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
53+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
5354
let root = sema.db.parse_or_expand(self.file)?;
5455
missing_record_expr_field_fix(
5556
&sema,
@@ -60,7 +61,7 @@ impl DiagnosticWithFix for NoSuchField {
6061
}
6162

6263
impl DiagnosticWithFix for MissingFields {
63-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
64+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
6465
// Note that although we could add a diagnostics to
6566
// fill the missing tuple field, e.g :
6667
// `struct A(usize);`
@@ -86,7 +87,8 @@ impl DiagnosticWithFix for MissingFields {
8687
.into_text_edit(&mut builder);
8788
builder.finish()
8889
};
89-
Some(Fix::new(
90+
Some(fix(
91+
"fill_missing_fields",
9092
"Fill struct fields",
9193
SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
9294
sema.original_range(&field_list_parent.syntax()).range,
@@ -95,20 +97,20 @@ impl DiagnosticWithFix for MissingFields {
9597
}
9698

9799
impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
98-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
100+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
99101
let root = sema.db.parse_or_expand(self.file)?;
100102
let tail_expr = self.expr.to_node(&root);
101103
let tail_expr_range = tail_expr.syntax().text_range();
102104
let replacement = format!("{}({})", self.required, tail_expr.syntax());
103105
let edit = TextEdit::replace(tail_expr_range, replacement);
104106
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
105107
let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
106-
Some(Fix::new(name, source_change, tail_expr_range))
108+
Some(fix("wrap_tail_expr", name, source_change, tail_expr_range))
107109
}
108110
}
109111

110112
impl DiagnosticWithFix for RemoveThisSemicolon {
111-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
113+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
112114
let root = sema.db.parse_or_expand(self.file)?;
113115

114116
let semicolon = self
@@ -123,12 +125,12 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
123125
let edit = TextEdit::delete(semicolon);
124126
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
125127

126-
Some(Fix::new("Remove this semicolon", source_change, semicolon))
128+
Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
127129
}
128130
}
129131

130132
impl DiagnosticWithFix for IncorrectCase {
131-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
133+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
132134
let root = sema.db.parse_or_expand(self.file)?;
133135
let name_node = self.ident.to_node(&root);
134136

@@ -140,12 +142,12 @@ impl DiagnosticWithFix for IncorrectCase {
140142
rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
141143

142144
let label = format!("Rename to {}", self.suggested_text);
143-
Some(Fix::new(&label, rename_changes, frange.range))
145+
Some(fix("change_case", &label, rename_changes, frange.range))
144146
}
145147
}
146148

147149
impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
148-
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
150+
fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
149151
let root = sema.db.parse_or_expand(self.file)?;
150152
let next_expr = self.next_expr.to_node(&root);
151153
let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
@@ -163,7 +165,8 @@ impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
163165

164166
let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
165167

166-
Some(Fix::new(
168+
Some(fix(
169+
"replace_with_find_map",
167170
"Replace filter_map(..).next() with find_map()",
168171
source_change,
169172
trigger_range,
@@ -175,7 +178,7 @@ fn missing_record_expr_field_fix(
175178
sema: &Semantics<RootDatabase>,
176179
usage_file_id: FileId,
177180
record_expr_field: &ast::RecordExprField,
178-
) -> Option<Fix> {
181+
) -> Option<Assist> {
179182
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
180183
let def_id = sema.resolve_variant(record_lit)?;
181184
let module;
@@ -233,7 +236,12 @@ fn missing_record_expr_field_fix(
233236
def_file_id,
234237
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
235238
);
236-
return Some(Fix::new("Create field", source_change, record_expr_field.syntax().text_range()));
239+
return Some(fix(
240+
"create_field",
241+
"Create field",
242+
source_change,
243+
record_expr_field.syntax().text_range(),
244+
));
237245

238246
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
239247
match field_def_list {

crates/ide/src/diagnostics/unlinked_file.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ use syntax::{
1616
};
1717
use text_edit::TextEdit;
1818

19-
use crate::Fix;
20-
21-
use super::fixes::DiagnosticWithFix;
19+
use crate::{
20+
diagnostics::{fix, fixes::DiagnosticWithFix},
21+
Assist,
22+
};
2223

2324
// Diagnostic: unlinked-file
2425
//
@@ -49,7 +50,7 @@ impl Diagnostic for UnlinkedFile {
4950
}
5051

5152
impl DiagnosticWithFix for UnlinkedFile {
52-
fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Fix> {
53+
fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Assist> {
5354
// If there's an existing module that could add a `mod` item to include the unlinked file,
5455
// suggest that as a fix.
5556

@@ -100,7 +101,7 @@ fn make_fix(
100101
parent_file_id: FileId,
101102
new_mod_name: &str,
102103
added_file_id: FileId,
103-
) -> Option<Fix> {
104+
) -> Option<Assist> {
104105
fn is_outline_mod(item: &ast::Item) -> bool {
105106
matches!(item, ast::Item::Module(m) if m.item_list().is_none())
106107
}
@@ -152,7 +153,8 @@ fn make_fix(
152153

153154
let edit = builder.finish();
154155
let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
155-
Some(Fix::new(
156+
Some(fix(
157+
"add_mod_declaration",
156158
&format!("Insert `{}`", mod_decl),
157159
SourceChange::from_text_edit(parent_file_id, edit),
158160
trigger_range,

crates/ide/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use crate::display::ToNav;
6969
pub use crate::{
7070
annotations::{Annotation, AnnotationConfig, AnnotationKind},
7171
call_hierarchy::CallItem,
72-
diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
72+
diagnostics::{Diagnostic, DiagnosticsConfig, Severity},
7373
display::navigation_target::NavigationTarget,
7474
expand_macro::ExpandedMacro,
7575
file_structure::{StructureNode, StructureNodeKind},

0 commit comments

Comments
 (0)