Skip to main content

ide_diagnostics/handlers/
trait_impl_redundant_assoc_item.rs

1use 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
15// Diagnostic: trait-impl-redundant-assoc_item
16//
17// Diagnoses redundant trait items in a trait impl.
18pub(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                // FIXME cannot generate generic parameter and bounds
54                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
75/// add assoc item into the trait def body
76fn 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        // don't modify trait def in outer crate
87        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}