ide_diagnostics/handlers/
trait_impl_redundant_assoc_item.rs1use hir::{HasSource, HirDisplay, db::ExpandDatabase};
2use ide_db::text_edit::TextRange;
3use ide_db::{
4 assists::{Assist, AssistId},
5 label::Label,
6 source_change::SourceChangeBuilder,
7};
8use syntax::ToSmolStr;
9
10use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
11
12pub(crate) fn trait_impl_redundant_assoc_item(
16 ctx: &DiagnosticsContext<'_>,
17 d: &hir::TraitImplRedundantAssocItems,
18) -> Diagnostic {
19 let db = ctx.sema.db;
20 let name = d.assoc_item.0.clone();
21 let redundant_assoc_item_name = name.display(db, ctx.edition);
22 let assoc_item = d.assoc_item.1;
23
24 let default_range = d.impl_.syntax_node_ptr().text_range();
25 let trait_name = d.trait_.name(db).display_no_db(ctx.edition).to_smolstr();
26
27 let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
28 hir::AssocItem::Function(id) => {
29 let function = id;
30 (
31 format!("`fn {redundant_assoc_item_name}`"),
32 function.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
33 format!("\n {};", function.display(db, ctx.display_target)),
34 )
35 }
36 hir::AssocItem::Const(id) => {
37 let constant = id;
38 (
39 format!("`const {redundant_assoc_item_name}`"),
40 constant.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
41 format!("\n {};", constant.display(db, ctx.display_target)),
42 )
43 }
44 hir::AssocItem::TypeAlias(id) => {
45 let type_alias = id;
46 (
47 format!("`type {redundant_assoc_item_name}`"),
48 type_alias.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
49 format!(
50 "\n type {};",
51 type_alias.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()
52 ),
53 )
54 }
55 };
56
57 let hir::FileRange { file_id, range } =
58 hir::InFile::new(d.file_id, diagnostic_range).original_node_file_range_rooted(db);
59 Diagnostic::new(
60 DiagnosticCode::RustcHardError("E0407"),
61 format!("{redundant_item_name} is not a member of trait `{trait_name}`"),
62 ide_db::FileRange { file_id: file_id.file_id(ctx.sema.db), range },
63 )
64 .stable()
65 .with_fixes(quickfix_for_redundant_assoc_item(
66 ctx,
67 d,
68 redundant_item_def,
69 diagnostic_range,
70 ))
71}
72
73fn quickfix_for_redundant_assoc_item(
75 ctx: &DiagnosticsContext<'_>,
76 d: &hir::TraitImplRedundantAssocItems,
77 redundant_item_def: String,
78 range: TextRange,
79) -> Option<Vec<Assist>> {
80 let file_id = d.file_id.file_id()?;
81 let add_assoc_item_def = |builder: &mut SourceChangeBuilder| -> Option<()> {
82 let db = ctx.sema.db;
83 let root = db.parse_or_expand(d.file_id);
84 let current_crate = ctx.sema.scope(&d.impl_.syntax_node_ptr().to_node(&root))?.krate();
86 let trait_def_crate = d.trait_.module(db).krate(db);
87 if trait_def_crate != current_crate {
88 return None;
89 }
90
91 let trait_def = d.trait_.source(db)?.value;
92 let l_curly = trait_def.assoc_item_list()?.l_curly_token()?.text_range();
93 let where_to_insert =
94 hir::InFile::new(d.file_id, l_curly).original_node_file_range_rooted_opt(db)?;
95 if where_to_insert.file_id != file_id {
96 return None;
97 }
98
99 builder.insert(where_to_insert.range.end(), redundant_item_def);
100 Some(())
101 };
102 let mut source_change_builder = SourceChangeBuilder::new(file_id.file_id(ctx.sema.db));
103 add_assoc_item_def(&mut source_change_builder)?;
104
105 Some(vec![Assist {
106 id: AssistId::quick_fix("add assoc item def into trait def"),
107 label: Label::new("Add assoc item def into trait def".to_owned()),
108 group: None,
109 target: range,
110 source_change: Some(source_change_builder.finish()),
111 command: None,
112 }])
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::tests::{check_diagnostics, check_fix, check_no_fix};
118
119 #[test]
120 fn quickfix_for_assoc_func() {
121 check_fix(
122 r#"
123trait Marker {
124 fn boo();
125}
126struct Foo;
127impl Marker for Foo {
128 fn$0 bar(_a: i32, _b: String) -> String {}
129 fn boo() {}
130}
131 "#,
132 r#"
133trait Marker {
134 fn bar(_a: i32, _b: String) -> String;
135 fn boo();
136}
137struct Foo;
138impl Marker for Foo {
139 fn bar(_a: i32, _b: String) -> String {}
140 fn boo() {}
141}
142 "#,
143 )
144 }
145
146 #[test]
147 fn quickfix_for_assoc_const() {
148 check_fix(
149 r#"
150trait Marker {
151 fn foo () {}
152}
153struct Foo;
154impl Marker for Foo {
155 const FLAG: bool$0 = false;
156}
157 "#,
158 r#"
159trait Marker {
160 const FLAG: bool;
161 fn foo () {}
162}
163struct Foo;
164impl Marker for Foo {
165 const FLAG: bool = false;
166}
167 "#,
168 )
169 }
170
171 #[test]
172 fn quickfix_for_assoc_type() {
173 check_fix(
174 r#"
175trait Marker {
176}
177struct Foo;
178impl Marker for Foo {
179 type T = i32;$0
180}
181 "#,
182 r#"
183trait Marker {
184 type T;
185}
186struct Foo;
187impl Marker for Foo {
188 type T = i32;
189}
190 "#,
191 )
192 }
193
194 #[test]
195 fn quickfix_dont_work() {
196 check_no_fix(
197 r#"
198 //- /dep.rs crate:dep
199 trait Marker {
200 }
201 //- /main.rs crate:main deps:dep
202 struct Foo;
203 impl dep::Marker for Foo {
204 type T = i32;$0
205 }
206 "#,
207 )
208 }
209
210 #[test]
211 fn trait_with_default_value() {
212 check_diagnostics(
213 r#"
214trait Marker {
215 const FLAG: bool = false;
216 fn boo();
217 fn foo () {}
218}
219struct Foo;
220impl Marker for Foo {
221 type T = i32;
222 //^^^^^^^^^^^^^ 💡 error: `type T` is not a member of trait `Marker`
223
224 const FLAG: bool = true;
225
226 fn bar() {}
227 //^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker`
228
229 fn boo() {}
230}
231 "#,
232 )
233 }
234
235 #[test]
236 fn dont_work_for_negative_impl() {
237 check_diagnostics(
238 r#"
239trait Marker {
240 const FLAG: bool = false;
241 fn boo();
242 fn foo () {}
243}
244struct Foo;
245impl !Marker for Foo {
246 type T = i32;
247 const FLAG: bool = true;
248 fn bar() {}
249 fn boo() {}
250}
251 "#,
252 )
253 }
254}