Auto merge of #16223 - Young-Flash:quickfix_redundant_assoc_item, r=Veykril
feat: add quickfix for redundant_assoc_item diagnostic Happy New Year 😊 follow up https://github.com/rust-lang/rust-analyzer/pull/15990, now it's time to close https://github.com/rust-lang/rust-analyzer/issues/15958, closes https://github.com/rust-lang/rust-analyzer/issues/16269 ![demo](https://github.com/rust-lang/rust-analyzer/assets/71162630/74022c52-1566-49a0-9be8-03b82f3e730f) EDIT: add a demo.git would be more illustrated when making release change log.
This commit is contained in:
commit
2980d54160
@ -1,5 +1,10 @@
|
||||
use hir::{Const, Function, HasSource, TypeAlias};
|
||||
use ide_db::base_db::FileRange;
|
||||
use hir::{db::ExpandDatabase, Const, Function, HasSource, HirDisplay, TypeAlias};
|
||||
use ide_db::{
|
||||
assists::{Assist, AssistId, AssistKind},
|
||||
label::Label,
|
||||
source_change::SourceChangeBuilder,
|
||||
};
|
||||
use text_edit::TextRange;
|
||||
|
||||
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
|
||||
|
||||
@ -10,47 +15,195 @@ pub(crate) fn trait_impl_redundant_assoc_item(
|
||||
ctx: &DiagnosticsContext<'_>,
|
||||
d: &hir::TraitImplRedundantAssocItems,
|
||||
) -> Diagnostic {
|
||||
let name = d.assoc_item.0.clone();
|
||||
let assoc_item = d.assoc_item.1;
|
||||
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) = match assoc_item {
|
||||
hir::AssocItem::Function(id) => (
|
||||
format!("`fn {}`", name.display(db)),
|
||||
Function::from(id)
|
||||
.source(db)
|
||||
.map(|it| it.syntax().value.text_range())
|
||||
.unwrap_or(default_range),
|
||||
),
|
||||
hir::AssocItem::Const(id) => (
|
||||
format!("`const {}`", name.display(db)),
|
||||
Const::from(id)
|
||||
.source(db)
|
||||
.map(|it| it.syntax().value.text_range())
|
||||
.unwrap_or(default_range),
|
||||
),
|
||||
hir::AssocItem::TypeAlias(id) => (
|
||||
format!("`type {}`", name.display(db)),
|
||||
TypeAlias::from(id)
|
||||
.source(db)
|
||||
.map(|it| it.syntax().value.text_range())
|
||||
.unwrap_or(default_range),
|
||||
),
|
||||
let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
|
||||
hir::AssocItem::Function(id) => {
|
||||
let function = Function::from(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).to_string()),
|
||||
)
|
||||
}
|
||||
hir::AssocItem::Const(id) => {
|
||||
let constant = Const::from(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).to_string()),
|
||||
)
|
||||
}
|
||||
hir::AssocItem::TypeAlias(id) => {
|
||||
let type_alias = TypeAlias::from(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}`"),
|
||||
FileRange { file_id: d.file_id.file_id().unwrap(), range: diagnostic_range },
|
||||
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;
|
||||
|
||||
Some(builder.insert(where_to_insert.end(), redundant_item_def))
|
||||
};
|
||||
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;
|
||||
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() {
|
||||
@ -64,12 +217,12 @@ trait Marker {
|
||||
struct Foo;
|
||||
impl Marker for Foo {
|
||||
type T = i32;
|
||||
//^^^^^^^^^^^^^ error: `type T` is not a member of trait `Marker`
|
||||
//^^^^^^^^^^^^^ 💡 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`
|
||||
//^^^^^^^^^^^ 💡 error: `fn bar` is not a member of trait `Marker`
|
||||
|
||||
fn boo() {}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user