| use hir::{db::ExpandDatabase, HasSource, HirDisplay}; |
| use ide_db::{ |
| assists::{Assist, AssistId, AssistKind}, |
| label::Label, |
| source_change::SourceChangeBuilder, |
| }; |
| use text_edit::TextRange; |
| |
| use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; |
| |
| // Diagnostic: trait-impl-redundant-assoc_item |
| // |
| // Diagnoses redundant trait items in a trait impl. |
| pub(crate) fn trait_impl_redundant_assoc_item( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::TraitImplRedundantAssocItems, |
| ) -> Diagnostic { |
| let db = ctx.sema.db; |
| let name = d.assoc_item.0.clone(); |
| let redundant_assoc_item_name = name.display(db); |
| let assoc_item = d.assoc_item.1; |
| |
| let default_range = d.impl_.syntax_node_ptr().text_range(); |
| let trait_name = d.trait_.name(db).to_smol_str(); |
| |
| let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item { |
| hir::AssocItem::Function(id) => { |
| let function = id; |
| ( |
| format!("`fn {}`", redundant_assoc_item_name), |
| function |
| .source(db) |
| .map(|it| it.syntax().value.text_range()) |
| .unwrap_or(default_range), |
| format!("\n {};", function.display(db)), |
| ) |
| } |
| hir::AssocItem::Const(id) => { |
| let constant = id; |
| ( |
| format!("`const {}`", redundant_assoc_item_name), |
| constant |
| .source(db) |
| .map(|it| it.syntax().value.text_range()) |
| .unwrap_or(default_range), |
| format!("\n {};", constant.display(db)), |
| ) |
| } |
| hir::AssocItem::TypeAlias(id) => { |
| let type_alias = id; |
| ( |
| format!("`type {}`", redundant_assoc_item_name), |
| type_alias |
| .source(db) |
| .map(|it| it.syntax().value.text_range()) |
| .unwrap_or(default_range), |
| format!("\n type {};", type_alias.name(ctx.sema.db).to_smol_str()), |
| ) |
| } |
| }; |
| |
| Diagnostic::new( |
| DiagnosticCode::RustcHardError("E0407"), |
| format!("{redundant_item_name} is not a member of trait `{trait_name}`"), |
| hir::InFile::new(d.file_id, diagnostic_range).original_node_file_range_rooted(db), |
| ) |
| .with_fixes(quickfix_for_redundant_assoc_item( |
| ctx, |
| d, |
| redundant_item_def, |
| diagnostic_range, |
| )) |
| } |
| |
| /// add assoc item into the trait def body |
| fn quickfix_for_redundant_assoc_item( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::TraitImplRedundantAssocItems, |
| redundant_item_def: String, |
| range: TextRange, |
| ) -> Option<Vec<Assist>> { |
| let add_assoc_item_def = |builder: &mut SourceChangeBuilder| -> Option<()> { |
| let db = ctx.sema.db; |
| let root = db.parse_or_expand(d.file_id); |
| // don't modify trait def in outer crate |
| let current_crate = ctx.sema.scope(&d.impl_.syntax_node_ptr().to_node(&root))?.krate(); |
| let trait_def_crate = d.trait_.module(db).krate(); |
| if trait_def_crate != current_crate { |
| return None; |
| } |
| |
| let trait_def = d.trait_.source(db)?.value; |
| let l_curly = trait_def.assoc_item_list()?.l_curly_token()?.text_range(); |
| let where_to_insert = |
| hir::InFile::new(d.file_id, l_curly).original_node_file_range_rooted(db).range; |
| |
| builder.insert(where_to_insert.end(), redundant_item_def); |
| Some(()) |
| }; |
| let file_id = d.file_id.file_id()?; |
| let mut source_change_builder = SourceChangeBuilder::new(file_id); |
| add_assoc_item_def(&mut source_change_builder)?; |
| |
| Some(vec![Assist { |
| id: AssistId("add assoc item def into trait def", AssistKind::QuickFix), |
| label: Label::new("Add assoc item def into trait def".to_string()), |
| group: None, |
| target: range, |
| source_change: Some(source_change_builder.finish()), |
| trigger_signature_help: false, |
| }]) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::tests::{check_diagnostics, check_fix, check_no_fix}; |
| |
| #[test] |
| fn quickfix_for_assoc_func() { |
| check_fix( |
| r#" |
| trait Marker { |
| fn boo(); |
| } |
| struct Foo; |
| impl Marker for Foo { |
| fn$0 bar(_a: i32, _b: String) -> String {} |
| fn boo() {} |
| } |
| "#, |
| r#" |
| trait Marker { |
| fn bar(_a: i32, _b: String) -> String; |
| fn boo(); |
| } |
| struct Foo; |
| impl Marker for Foo { |
| fn bar(_a: i32, _b: String) -> String {} |
| fn boo() {} |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn quickfix_for_assoc_const() { |
| check_fix( |
| r#" |
| trait Marker { |
| fn foo () {} |
| } |
| struct Foo; |
| impl Marker for Foo { |
| const FLAG: bool$0 = false; |
| } |
| "#, |
| r#" |
| trait Marker { |
| const FLAG: bool; |
| fn foo () {} |
| } |
| struct Foo; |
| impl Marker for Foo { |
| const FLAG: bool = false; |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn quickfix_for_assoc_type() { |
| check_fix( |
| r#" |
| trait Marker { |
| } |
| struct Foo; |
| impl Marker for Foo { |
| type T = i32;$0 |
| } |
| "#, |
| r#" |
| trait Marker { |
| type T; |
| } |
| struct Foo; |
| impl Marker for Foo { |
| type T = i32; |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn quickfix_dont_work() { |
| check_no_fix( |
| r#" |
| //- /dep.rs crate:dep |
| trait Marker { |
| } |
| //- /main.rs crate:main deps:dep |
| struct Foo; |
| impl dep::Marker for Foo { |
| type T = i32;$0 |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn trait_with_default_value() { |
| check_diagnostics( |
| r#" |
| trait Marker { |
| const FLAG: bool = false; |
| fn boo(); |
| fn foo () {} |
| } |
| struct Foo; |
| impl Marker for Foo { |
| type T = i32; |
| //^^^^^^^^^^^^^ 💡 error: `type T` is not a member of trait `Marker` |
| |
| const FLAG: bool = true; |
| |
| fn bar() {} |
| //^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker` |
| |
| fn boo() {} |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn dont_work_for_negative_impl() { |
| check_diagnostics( |
| r#" |
| trait Marker { |
| const FLAG: bool = false; |
| fn boo(); |
| fn foo () {} |
| } |
| struct Foo; |
| impl !Marker for Foo { |
| type T = i32; |
| const FLAG: bool = true; |
| fn bar() {} |
| fn boo() {} |
| } |
| "#, |
| ) |
| } |
| } |