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::{
9 AstNode, ToSmolStr,
10 ast::{HasName, edit::AstNodeEdit},
11};
12
13use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
14
15pub(crate) fn trait_impl_redundant_assoc_item(
19 ctx: &DiagnosticsContext<'_, '_>,
20 d: &hir::TraitImplRedundantAssocItems,
21) -> Diagnostic {
22 let db = ctx.sema.db;
23 let name = d.assoc_item.0.clone();
24 let redundant_assoc_item_name = name.display(db, ctx.edition);
25 let assoc_item = d.assoc_item.1;
26
27 let default_range = d.impl_.syntax_node_ptr().text_range();
28 let trait_name = d.trait_.name(db).display_no_db(ctx.edition).to_smolstr();
29 let indent_level = d.trait_.source(db).map_or(0, |it| it.value.indent_level().0) + 1;
30
31 let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
32 hir::AssocItem::Function(id) => {
33 let function = id;
34 (
35 format!("`fn {redundant_assoc_item_name}`"),
36 function.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
37 format!("\n{};", function.display(db, ctx.display_target)),
38 )
39 }
40 hir::AssocItem::Const(id) => {
41 let constant = id;
42 (
43 format!("`const {redundant_assoc_item_name}`"),
44 constant.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
45 format!("\n{};", constant.display(db, ctx.display_target)),
46 )
47 }
48 hir::AssocItem::TypeAlias(id) => {
49 let type_alias = id;
50 (
51 format!("`type {redundant_assoc_item_name}`"),
52 type_alias.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
53 format!("\ntype {};", type_alias.name(ctx.sema.db).display_no_db(ctx.edition)),
55 )
56 }
57 };
58
59 let hir::FileRange { file_id, range } =
60 hir::InFile::new(d.file_id, diagnostic_range).original_node_file_range_rooted(db);
61 Diagnostic::new(
62 DiagnosticCode::RustcHardError("E0407"),
63 format!("{redundant_item_name} is not a member of trait `{trait_name}`"),
64 ide_db::FileRange { file_id: file_id.file_id(ctx.sema.db), range },
65 )
66 .stable()
67 .with_fixes(quickfix_for_redundant_assoc_item(
68 ctx,
69 d,
70 stdx::indent_string(&redundant_item_def, indent_level),
71 diagnostic_range,
72 ))
73}
74
75fn 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 file_id = d.file_id.file_id()?;
83 let add_assoc_item_def = |builder: &mut SourceChangeBuilder| -> Option<()> {
84 let db = ctx.sema.db;
85 let root = db.parse_or_expand(d.file_id);
86 let impl_def = d.impl_.to_node(&root);
88 let current_crate = ctx.sema.scope(impl_def.syntax())?.krate();
89 let trait_def_crate = d.trait_.module(db).krate(db);
90 if trait_def_crate != current_crate {
91 return None;
92 }
93
94 let trait_def = d.trait_.source(db)?.value;
95 let insert_after = find_insert_after(range, &impl_def, &trait_def)?;
96
97 let where_to_insert =
98 hir::InFile::new(d.file_id, insert_after).original_node_file_range_rooted_opt(db)?;
99 if where_to_insert.file_id != file_id {
100 return None;
101 }
102
103 builder.insert(where_to_insert.range.end(), redundant_item_def);
104 Some(())
105 };
106 let mut source_change_builder = SourceChangeBuilder::new(file_id.file_id(ctx.sema.db));
107 add_assoc_item_def(&mut source_change_builder)?;
108
109 Some(vec![Assist {
110 id: AssistId::quick_fix("add assoc item def into trait def"),
111 label: Label::new("Add assoc item def into trait def".to_owned()),
112 group: None,
113 target: range,
114 source_change: Some(source_change_builder.finish()),
115 command: None,
116 }])
117}
118
119fn find_insert_after(
120 redundant_range: TextRange,
121 impl_def: &syntax::ast::Impl,
122 trait_def: &syntax::ast::Trait,
123) -> Option<TextRange> {
124 let impl_items_before_redundant = impl_def
125 .assoc_item_list()?
126 .assoc_items()
127 .take_while(|it| it.syntax().text_range().start() < redundant_range.start())
128 .filter_map(|it| name_of(&it))
129 .collect::<Vec<_>>();
130
131 let after_item = trait_def
132 .assoc_item_list()?
133 .assoc_items()
134 .filter(|it| {
135 name_of(it).is_some_and(|name| {
136 impl_items_before_redundant.iter().any(|it| it.text() == name.text())
137 })
138 })
139 .last()
140 .map(|it| it.syntax().text_range());
141
142 return after_item.or_else(|| Some(trait_def.assoc_item_list()?.l_curly_token()?.text_range()));
143
144 fn name_of(it: &syntax::ast::AssocItem) -> Option<syntax::ast::Name> {
145 match it {
146 syntax::ast::AssocItem::Const(it) => it.name(),
147 syntax::ast::AssocItem::Fn(it) => it.name(),
148 syntax::ast::AssocItem::TypeAlias(it) => it.name(),
149 syntax::ast::AssocItem::MacroCall(_) => None,
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::tests::{check_diagnostics, check_fix, check_no_fix};
157
158 #[test]
159 fn quickfix_for_assoc_func() {
160 check_fix(
161 r#"
162trait Marker {
163 fn boo();
164}
165struct Foo;
166impl Marker for Foo {
167 fn$0 bar(_a: i32, _b: String) -> String {}
168 fn boo() {}
169}
170 "#,
171 r#"
172trait Marker {
173 fn bar(_a: i32, _b: String) -> String;
174 fn boo();
175}
176struct Foo;
177impl Marker for Foo {
178 fn bar(_a: i32, _b: String) -> String {}
179 fn boo() {}
180}
181 "#,
182 )
183 }
184
185 #[test]
186 fn quickfix_for_assoc_const() {
187 check_fix(
188 r#"
189trait Marker {
190 fn foo () {}
191}
192struct Foo;
193impl Marker for Foo {
194 const FLAG: bool$0 = false;
195}
196 "#,
197 r#"
198trait Marker {
199 const FLAG: bool;
200 fn foo () {}
201}
202struct Foo;
203impl Marker for Foo {
204 const FLAG: bool = false;
205}
206 "#,
207 )
208 }
209
210 #[test]
211 fn quickfix_for_assoc_type() {
212 check_fix(
213 r#"
214trait Marker {
215}
216struct Foo;
217impl Marker for Foo {
218 type T = i32;$0
219}
220 "#,
221 r#"
222trait Marker {
223 type T;
224}
225struct Foo;
226impl Marker for Foo {
227 type T = i32;
228}
229 "#,
230 )
231 }
232
233 #[test]
234 fn quickfix_indentations() {
235 check_fix(
236 r#"
237mod indent {
238 trait Marker {
239 fn boo();
240 }
241 struct Foo;
242 impl Marker for Foo {
243 fn$0 bar<T: Copy>(_a: i32, _b: T) -> String {}
244 fn boo() {}
245 }
246}
247 "#,
248 r#"
249mod indent {
250 trait Marker {
251 fn bar<T>(_a: i32, _b: T) -> String
252 where
253 T: Copy,;
254 fn boo();
255 }
256 struct Foo;
257 impl Marker for Foo {
258 fn bar<T: Copy>(_a: i32, _b: T) -> String {}
259 fn boo() {}
260 }
261}
262 "#,
263 );
264
265 check_fix(
266 r#"
267mod indent {
268 trait Marker {
269 fn foo () {}
270 }
271 struct Foo;
272 impl Marker for Foo {
273 const FLAG: bool$0 = false;
274 }
275}
276 "#,
277 r#"
278mod indent {
279 trait Marker {
280 const FLAG: bool;
281 fn foo () {}
282 }
283 struct Foo;
284 impl Marker for Foo {
285 const FLAG: bool = false;
286 }
287}
288 "#,
289 );
290
291 check_fix(
292 r#"
293mod indent {
294 trait Marker {
295 }
296 struct Foo;
297 impl Marker for Foo {
298 type T = i32;$0
299 }
300}
301 "#,
302 r#"
303mod indent {
304 trait Marker {
305 type T;
306 }
307 struct Foo;
308 impl Marker for Foo {
309 type T = i32;
310 }
311}
312 "#,
313 );
314 }
315
316 #[test]
317 fn quickfix_order() {
318 check_fix(
319 r#"
320trait Marker {
321 fn foo();
322 fn baz();
323}
324struct Foo;
325impl Marker for Foo {
326 fn foo() {}
327 fn missing() {}$0
328 fn baz() {}
329}
330 "#,
331 r#"
332trait Marker {
333 fn foo();
334 fn missing();
335 fn baz();
336}
337struct Foo;
338impl Marker for Foo {
339 fn foo() {}
340 fn missing() {}
341 fn baz() {}
342}
343 "#,
344 );
345
346 check_fix(
347 r#"
348trait Marker {
349 type Item;
350 fn bar();
351 fn baz();
352}
353struct Foo;
354impl Marker for Foo {
355 type Item = Foo;
356 fn missing() {}$0
357 fn bar() {}
358 fn baz() {}
359}
360 "#,
361 r#"
362trait Marker {
363 type Item;
364 fn missing();
365 fn bar();
366 fn baz();
367}
368struct Foo;
369impl Marker for Foo {
370 type Item = Foo;
371 fn missing() {}
372 fn bar() {}
373 fn baz() {}
374}
375 "#,
376 );
377 }
378
379 #[test]
380 fn quickfix_dont_work() {
381 check_no_fix(
382 r#"
383 //- /dep.rs crate:dep
384 trait Marker {
385 }
386 //- /main.rs crate:main deps:dep
387 struct Foo;
388 impl dep::Marker for Foo {
389 type T = i32;$0
390 }
391 "#,
392 )
393 }
394
395 #[test]
396 fn trait_with_default_value() {
397 check_diagnostics(
398 r#"
399trait Marker {
400 const FLAG: bool = false;
401 fn boo();
402 fn foo () {}
403}
404struct Foo;
405impl Marker for Foo {
406 type T = i32;
407 //^^^^^^^^^^^^^ 💡 error: `type T` is not a member of trait `Marker`
408
409 const FLAG: bool = true;
410
411 fn bar() {}
412 //^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker`
413
414 fn boo() {}
415}
416 "#,
417 )
418 }
419
420 #[test]
421 fn dont_work_for_negative_impl() {
422 check_diagnostics(
423 r#"
424trait Marker {
425 const FLAG: bool = false;
426 fn boo();
427 fn foo () {}
428}
429struct Foo;
430impl !Marker for Foo {
431 type T = i32;
432 const FLAG: bool = true;
433 fn bar() {}
434 fn boo() {}
435}
436 "#,
437 )
438 }
439}