Skip to content

Commit 2980d54

Browse files
committed
Auto merge of rust-lang#16223 - Young-Flash:quickfix_redundant_assoc_item, r=Veykril
feat: add quickfix for redundant_assoc_item diagnostic Happy New Year 😊 follow up rust-lang/rust-analyzer#15990, now it's time to close rust-lang/rust-analyzer#15958, closes rust-lang/rust-analyzer#16269 ![demo](https://github.com/rust-lang/rust-analyzer/assets/71162630/74022c52-1566-49a0-9be8-03b82f3e730f) EDIT: add a demo.git would be more illustrated when making release change log.
2 parents 1c6755c + c5c1360 commit 2980d54

File tree

1 file changed

+182
-29
lines changed

1 file changed

+182
-29
lines changed

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

+182-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
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+
label::Label,
5+
source_change::SourceChangeBuilder,
6+
};
7+
use text_edit::TextRange;
38

49
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
510

@@ -10,47 +15,195 @@ pub(crate) fn trait_impl_redundant_assoc_item(
1015
ctx: &DiagnosticsContext<'_>,
1116
d: &hir::TraitImplRedundantAssocItems,
1217
) -> Diagnostic {
18+
let db = ctx.sema.db;
1319
let name = d.assoc_item.0.clone();
20+
let redundant_assoc_item_name = name.display(db);
1421
let assoc_item = d.assoc_item.1;
15-
let db = ctx.sema.db;
1622

1723
let default_range = d.impl_.syntax_node_ptr().text_range();
1824
let trait_name = d.trait_.name(db).to_smol_str();
1925

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

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

51113
#[cfg(test)]
52114
mod tests {
53-
use crate::tests::check_diagnostics;
115+
use crate::tests::{check_diagnostics, check_fix, check_no_fix};
116+
117+
#[test]
118+
fn quickfix_for_assoc_func() {
119+
check_fix(
120+
r#"
121+
trait Marker {
122+
fn boo();
123+
}
124+
struct Foo;
125+
impl Marker for Foo {
126+
fn$0 bar(_a: i32, _b: String) -> String {}
127+
fn boo() {}
128+
}
129+
"#,
130+
r#"
131+
trait Marker {
132+
fn bar(_a: i32, _b: String) -> String;
133+
fn boo();
134+
}
135+
struct Foo;
136+
impl Marker for Foo {
137+
fn bar(_a: i32, _b: String) -> String {}
138+
fn boo() {}
139+
}
140+
"#,
141+
)
142+
}
143+
144+
#[test]
145+
fn quickfix_for_assoc_const() {
146+
check_fix(
147+
r#"
148+
trait Marker {
149+
fn foo () {}
150+
}
151+
struct Foo;
152+
impl Marker for Foo {
153+
const FLAG: bool$0 = false;
154+
}
155+
"#,
156+
r#"
157+
trait Marker {
158+
const FLAG: bool;
159+
fn foo () {}
160+
}
161+
struct Foo;
162+
impl Marker for Foo {
163+
const FLAG: bool = false;
164+
}
165+
"#,
166+
)
167+
}
168+
169+
#[test]
170+
fn quickfix_for_assoc_type() {
171+
check_fix(
172+
r#"
173+
trait Marker {
174+
}
175+
struct Foo;
176+
impl Marker for Foo {
177+
type T = i32;$0
178+
}
179+
"#,
180+
r#"
181+
trait Marker {
182+
type T;
183+
}
184+
struct Foo;
185+
impl Marker for Foo {
186+
type T = i32;
187+
}
188+
"#,
189+
)
190+
}
191+
192+
#[test]
193+
fn quickfix_dont_work() {
194+
check_no_fix(
195+
r#"
196+
//- /dep.rs crate:dep
197+
trait Marker {
198+
}
199+
//- /main.rs crate:main deps:dep
200+
struct Foo;
201+
impl dep::Marker for Foo {
202+
type T = i32;$0
203+
}
204+
"#,
205+
)
206+
}
54207

55208
#[test]
56209
fn trait_with_default_value() {
@@ -64,12 +217,12 @@ trait Marker {
64217
struct Foo;
65218
impl Marker for Foo {
66219
type T = i32;
67-
//^^^^^^^^^^^^^ error: `type T` is not a member of trait `Marker`
220+
//^^^^^^^^^^^^^ 💡 error: `type T` is not a member of trait `Marker`
68221
69222
const FLAG: bool = true;
70223
71224
fn bar() {}
72-
//^^^^^^^^^^^ error: `fn bar` is not a member of trait `Marker`
225+
//^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker`
73226
74227
fn boo() {}
75228
}

0 commit comments

Comments
 (0)