Skip to content

Commit 613774e

Browse files
committed
feat: add quickfix for redundant_assoc_item diagnostic
1 parent cf52c4b commit 613774e

File tree

1 file changed

+90
-25
lines changed

1 file changed

+90
-25
lines changed

crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
use hir::{Const, Function, HasSource, TypeAlias};
2-
use ide_db::base_db::FileRange;
1+
use hir::{db::ExpandDatabase, Const, Function, HasSource, HirDisplay, TypeAlias};
2+
use ide_db::{
3+
assists::{Assist, AssistId, AssistKind},
4+
base_db::FileRange,
5+
label::Label,
6+
source_change::SourceChangeBuilder,
7+
};
8+
use syntax::{AstNode, SyntaxKind};
9+
use text_edit::TextRange;
310

411
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
512

@@ -10,42 +17,100 @@ pub(crate) fn trait_impl_redundant_assoc_item(
1017
ctx: &DiagnosticsContext<'_>,
1118
d: &hir::TraitImplRedundantAssocItems,
1219
) -> Diagnostic {
20+
let db = ctx.sema.db;
1321
let name = d.assoc_item.0.clone();
22+
let redundant_assoc_item_name = name.display(db);
1423
let assoc_item = d.assoc_item.1;
15-
let db = ctx.sema.db;
1624

1725
let default_range = d.impl_.syntax_node_ptr().text_range();
1826
let trait_name = d.trait_.name(db).to_smol_str();
1927

20-
let (redundant_item_name, diagnostic_range) = match assoc_item {
21-
hir::AssocItem::Function(id) => (
22-
format!("`fn {}`", name.display(db)),
23-
Function::from(id)
24-
.source(db)
25-
.map(|it| it.syntax().value.text_range())
26-
.unwrap_or(default_range),
27-
),
28-
hir::AssocItem::Const(id) => (
29-
format!("`const {}`", name.display(db)),
30-
Const::from(id)
31-
.source(db)
32-
.map(|it| it.syntax().value.text_range())
33-
.unwrap_or(default_range),
34-
),
35-
hir::AssocItem::TypeAlias(id) => (
36-
format!("`type {}`", name.display(db)),
37-
TypeAlias::from(id)
38-
.source(db)
39-
.map(|it| it.syntax().value.text_range())
40-
.unwrap_or(default_range),
41-
),
28+
let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
29+
hir::AssocItem::Function(id) => {
30+
let function = Function::from(id);
31+
(
32+
format!("`fn {}`", redundant_assoc_item_name),
33+
function
34+
.source(db)
35+
.map(|it| it.syntax().value.text_range())
36+
.unwrap_or(default_range),
37+
format!("\n {};", function.display(db).to_string()),
38+
)
39+
}
40+
hir::AssocItem::Const(id) => {
41+
let constant = Const::from(id);
42+
(
43+
format!("`const {}`", redundant_assoc_item_name),
44+
constant
45+
.source(db)
46+
.map(|it| it.syntax().value.text_range())
47+
.unwrap_or(default_range),
48+
format!("\n {};", constant.display(db).to_string()),
49+
)
50+
}
51+
hir::AssocItem::TypeAlias(id) => {
52+
let type_alias = TypeAlias::from(id);
53+
(
54+
format!("`type {}`", redundant_assoc_item_name),
55+
type_alias
56+
.source(db)
57+
.map(|it| it.syntax().value.text_range())
58+
.unwrap_or(default_range),
59+
format!("\n type {};", type_alias.name(ctx.sema.db).to_smol_str()),
60+
)
61+
}
4262
};
4363

4464
Diagnostic::new(
4565
DiagnosticCode::RustcHardError("E0407"),
4666
format!("{redundant_item_name} is not a member of trait `{trait_name}`"),
4767
FileRange { file_id: d.file_id.file_id().unwrap(), range: diagnostic_range },
4868
)
69+
.with_fixes(quickfix_for_redundant_assoc_item(
70+
ctx,
71+
d,
72+
redundant_item_def,
73+
diagnostic_range,
74+
))
75+
}
76+
77+
/// add assoc item into the trait def body
78+
fn quickfix_for_redundant_assoc_item(
79+
ctx: &DiagnosticsContext<'_>,
80+
d: &hir::TraitImplRedundantAssocItems,
81+
redundant_item_def: String,
82+
range: TextRange,
83+
) -> Option<Vec<Assist>> {
84+
let add_assoc_item_def = |builder: &mut SourceChangeBuilder| -> Option<()> {
85+
let db = ctx.sema.db;
86+
let root = db.parse_or_expand(d.file_id);
87+
// don't modify trait def in outer crate
88+
let current_crate = ctx.sema.scope(&d.impl_.syntax_node_ptr().to_node(&root))?.krate();
89+
let trait_def_crate = d.trait_.module(db).krate();
90+
if trait_def_crate != current_crate {
91+
return None;
92+
}
93+
let trait_def = d.trait_.source(db)?.value;
94+
let where_to_insert = trait_def
95+
.syntax()
96+
.descendants_with_tokens()
97+
.find(|it| it.kind() == SyntaxKind::L_CURLY)
98+
.map(|it| it.text_range())?;
99+
100+
Some(builder.insert(where_to_insert.end(), redundant_item_def))
101+
};
102+
let file_id = d.file_id.file_id()?;
103+
let mut source_change_builder = SourceChangeBuilder::new(file_id);
104+
add_assoc_item_def(&mut source_change_builder)?;
105+
106+
Some(vec![Assist {
107+
id: AssistId("add assoc item def into trait def", AssistKind::QuickFix),
108+
label: Label::new("Add assoc item def into trait def".to_string()),
109+
group: None,
110+
target: range,
111+
source_change: Some(source_change_builder.finish()),
112+
trigger_signature_help: false,
113+
}])
49114
}
50115

51116
#[cfg(test)]

0 commit comments

Comments
 (0)