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::ToSmolStr;
9
10use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
11
12// Diagnostic: trait-impl-redundant-assoc_item
13//
14// Diagnoses redundant trait items in a trait impl.
15pub(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
73/// add assoc item into the trait def body
74fn 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        // don't modify trait def in outer crate
85        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}