diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs | 1250 |
1 files changed, 1250 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs new file mode 100644 index 000000000..bd50208da --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -0,0 +1,1250 @@ +use hir::{InFile, ModuleDef}; +use ide_db::{ + helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator, + syntax_helpers::insert_whitespace_into_node::insert_ws_into, +}; +use itertools::Itertools; +use syntax::{ + ast::{self, AstNode, HasName}, + SyntaxKind::WHITESPACE, +}; + +use crate::{ + assist_context::{AssistBuilder, AssistContext, Assists}, + utils::{ + add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, + generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, + }, + AssistId, AssistKind, +}; + +// Assist: replace_derive_with_manual_impl +// +// Converts a `derive` impl into a manual one. +// +// ``` +// # //- minicore: derive +// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; } +// #[derive(Deb$0ug, Display)] +// struct S; +// ``` +// -> +// ``` +// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; } +// #[derive(Display)] +// struct S; +// +// impl Debug for S { +// $0fn fmt(&self, f: &mut Formatter) -> Result<()> { +// f.debug_struct("S").finish() +// } +// } +// ``` +pub(crate) fn replace_derive_with_manual_impl( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let attr = ctx.find_node_at_offset_with_descend::<ast::Attr>()?; + let path = attr.path()?; + let hir_file = ctx.sema.hir_file_for(attr.syntax()); + if !hir_file.is_derive_attr_pseudo_expansion(ctx.db()) { + return None; + } + + let InFile { file_id, value } = hir_file.call_node(ctx.db())?; + if file_id.is_macro() { + // FIXME: make this work in macro files + return None; + } + // collect the derive paths from the #[derive] expansion + let current_derives = ctx + .sema + .parse_or_expand(hir_file)? + .descendants() + .filter_map(ast::Attr::cast) + .filter_map(|attr| attr.path()) + .collect::<Vec<_>>(); + + let adt = value.parent().and_then(ast::Adt::cast)?; + let attr = ast::Attr::cast(value)?; + let args = attr.token_tree()?; + + let current_module = ctx.sema.scope(adt.syntax())?.module(); + let current_crate = current_module.krate(); + + let found_traits = items_locator::items_with_name( + &ctx.sema, + current_crate, + NameToImport::exact_case_sensitive(path.segments().last()?.to_string()), + items_locator::AssocItemSearch::Exclude, + Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()), + ) + .filter_map(|item| match item.as_module_def()? { + ModuleDef::Trait(trait_) => Some(trait_), + _ => None, + }) + .flat_map(|trait_| { + current_module + .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) + .as_ref() + .map(mod_path_to_ast) + .zip(Some(trait_)) + }); + + let mut no_traits_found = true; + for (replace_trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { + add_assist( + acc, + ctx, + &attr, + ¤t_derives, + &args, + &path, + &replace_trait_path, + Some(trait_), + &adt, + )?; + } + if no_traits_found { + add_assist(acc, ctx, &attr, ¤t_derives, &args, &path, &path, None, &adt)?; + } + Some(()) +} + +fn add_assist( + acc: &mut Assists, + ctx: &AssistContext<'_>, + attr: &ast::Attr, + old_derives: &[ast::Path], + old_tree: &ast::TokenTree, + old_trait_path: &ast::Path, + replace_trait_path: &ast::Path, + trait_: Option<hir::Trait>, + adt: &ast::Adt, +) -> Option<()> { + let target = attr.syntax().text_range(); + let annotated_name = adt.name()?; + let label = format!("Convert to manual `impl {} for {}`", replace_trait_path, annotated_name); + + acc.add( + AssistId("replace_derive_with_manual_impl", AssistKind::Refactor), + label, + target, + |builder| { + let insert_pos = adt.syntax().text_range().end(); + let impl_def_with_items = + impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, replace_trait_path); + update_attribute(builder, old_derives, old_tree, old_trait_path, attr); + let trait_path = replace_trait_path.to_string(); + match (ctx.config.snippet_cap, impl_def_with_items) { + (None, _) => { + builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, "")) + } + (Some(cap), None) => builder.insert_snippet( + cap, + insert_pos, + generate_trait_impl_text(adt, &trait_path, " $0"), + ), + (Some(cap), Some((impl_def, first_assoc_item))) => { + let mut cursor = Cursor::Before(first_assoc_item.syntax()); + let placeholder; + if let ast::AssocItem::Fn(ref func) = first_assoc_item { + if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) + { + if m.syntax().text() == "todo!()" { + placeholder = m; + cursor = Cursor::Replace(placeholder.syntax()); + } + } + } + + builder.insert_snippet( + cap, + insert_pos, + format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)), + ) + } + }; + }, + ) +} + +fn impl_def_from_trait( + sema: &hir::Semantics<'_, ide_db::RootDatabase>, + adt: &ast::Adt, + annotated_name: &ast::Name, + trait_: Option<hir::Trait>, + trait_path: &ast::Path, +) -> Option<(ast::Impl, ast::AssocItem)> { + let trait_ = trait_?; + let target_scope = sema.scope(annotated_name.syntax())?; + let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No); + if trait_items.is_empty() { + return None; + } + let impl_def = { + use syntax::ast::Impl; + let text = generate_trait_impl_text(adt, trait_path.to_string().as_str(), ""); + let parse = syntax::SourceFile::parse(&text); + let node = match parse.tree().syntax().descendants().find_map(Impl::cast) { + Some(it) => it, + None => { + panic!( + "Failed to make ast node `{}` from text {}", + std::any::type_name::<Impl>(), + text + ) + } + }; + let node = node.clone_subtree(); + assert_eq!(node.syntax().text_range().start(), 0.into()); + node + }; + + let trait_items = trait_items + .into_iter() + .map(|it| { + if sema.hir_file_for(it.syntax()).is_macro() { + if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) { + return it; + } + } + it.clone_for_update() + }) + .collect(); + let (impl_def, first_assoc_item) = + add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); + + // Generate a default `impl` function body for the derived trait. + if let ast::AssocItem::Fn(ref func) = first_assoc_item { + let _ = gen_trait_fn_body(func, trait_path, adt); + }; + + Some((impl_def, first_assoc_item)) +} + +fn update_attribute( + builder: &mut AssistBuilder, + old_derives: &[ast::Path], + old_tree: &ast::TokenTree, + old_trait_path: &ast::Path, + attr: &ast::Attr, +) { + let new_derives = old_derives + .iter() + .filter(|t| t.to_string() != old_trait_path.to_string()) + .collect::<Vec<_>>(); + let has_more_derives = !new_derives.is_empty(); + + if has_more_derives { + let new_derives = format!("({})", new_derives.iter().format(", ")); + builder.replace(old_tree.syntax().text_range(), new_derives); + } else { + let attr_range = attr.syntax().text_range(); + builder.delete(attr_range); + + if let Some(line_break_range) = attr + .syntax() + .next_sibling_or_token() + .filter(|t| t.kind() == WHITESPACE) + .map(|t| t.text_range()) + { + builder.delete(line_break_range); + } + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn add_custom_impl_debug_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(Debu$0g)] +struct Foo { + bar: String, +} +"#, + r#" +struct Foo { + bar: String, +} + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Foo").field("bar", &self.bar).finish() + } +} +"#, + ) + } + #[test] + fn add_custom_impl_debug_tuple_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(Debu$0g)] +struct Foo(String, usize); +"#, + r#"struct Foo(String, usize); + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("Foo").field(&self.0).field(&self.1).finish() + } +} +"#, + ) + } + #[test] + fn add_custom_impl_debug_empty_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(Debu$0g)] +struct Foo; +"#, + r#" +struct Foo; + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Foo").finish() + } +} +"#, + ) + } + #[test] + fn add_custom_impl_debug_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(Debu$0g)] +enum Foo { + Bar, + Baz, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Bar => write!(f, "Bar"), + Self::Baz => write!(f, "Baz"), + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_debug_tuple_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(Debu$0g)] +enum Foo { + Bar(usize, usize), + Baz, +} +"#, + r#" +enum Foo { + Bar(usize, usize), + Baz, +} + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Bar(arg0, arg1) => f.debug_tuple("Bar").field(arg0).field(arg1).finish(), + Self::Baz => write!(f, "Baz"), + } + } +} +"#, + ) + } + #[test] + fn add_custom_impl_debug_record_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(Debu$0g)] +enum Foo { + Bar { + baz: usize, + qux: usize, + }, + Baz, +} +"#, + r#" +enum Foo { + Bar { + baz: usize, + qux: usize, + }, + Baz, +} + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Bar { baz, qux } => f.debug_struct("Bar").field("baz", baz).field("qux", qux).finish(), + Self::Baz => write!(f, "Baz"), + } + } +} +"#, + ) + } + #[test] + fn add_custom_impl_default_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: default, derive +#[derive(Defau$0lt)] +struct Foo { + foo: usize, +} +"#, + r#" +struct Foo { + foo: usize, +} + +impl Default for Foo { + $0fn default() -> Self { + Self { foo: Default::default() } + } +} +"#, + ) + } + #[test] + fn add_custom_impl_default_tuple_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: default, derive +#[derive(Defau$0lt)] +struct Foo(usize); +"#, + r#" +struct Foo(usize); + +impl Default for Foo { + $0fn default() -> Self { + Self(Default::default()) + } +} +"#, + ) + } + #[test] + fn add_custom_impl_default_empty_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: default, derive +#[derive(Defau$0lt)] +struct Foo; +"#, + r#" +struct Foo; + +impl Default for Foo { + $0fn default() -> Self { + Self { } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_hash_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: hash, derive +#[derive(Has$0h)] +struct Foo { + bin: usize, + bar: usize, +} +"#, + r#" +struct Foo { + bin: usize, + bar: usize, +} + +impl core::hash::Hash for Foo { + $0fn hash<H: core::hash::Hasher>(&self, state: &mut H) { + self.bin.hash(state); + self.bar.hash(state); + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_hash_tuple_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: hash, derive +#[derive(Has$0h)] +struct Foo(usize, usize); +"#, + r#" +struct Foo(usize, usize); + +impl core::hash::Hash for Foo { + $0fn hash<H: core::hash::Hasher>(&self, state: &mut H) { + self.0.hash(state); + self.1.hash(state); + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_hash_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: hash, derive +#[derive(Has$0h)] +enum Foo { + Bar, + Baz, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} + +impl core::hash::Hash for Foo { + $0fn hash<H: core::hash::Hasher>(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +struct Foo { + bin: usize, + bar: usize, +} +"#, + r#" +struct Foo { + bin: usize, + bar: usize, +} + +impl Clone for Foo { + $0fn clone(&self) -> Self { + Self { bin: self.bin.clone(), bar: self.bar.clone() } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_tuple_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +struct Foo(usize, usize); +"#, + r#" +struct Foo(usize, usize); + +impl Clone for Foo { + $0fn clone(&self) -> Self { + Self(self.0.clone(), self.1.clone()) + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_empty_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +struct Foo; +"#, + r#" +struct Foo; + +impl Clone for Foo { + $0fn clone(&self) -> Self { + Self { } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +enum Foo { + Bar, + Baz, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} + +impl Clone for Foo { + $0fn clone(&self) -> Self { + match self { + Self::Bar => Self::Bar, + Self::Baz => Self::Baz, + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_tuple_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +enum Foo { + Bar(String), + Baz, +} +"#, + r#" +enum Foo { + Bar(String), + Baz, +} + +impl Clone for Foo { + $0fn clone(&self) -> Self { + match self { + Self::Bar(arg0) => Self::Bar(arg0.clone()), + Self::Baz => Self::Baz, + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_record_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +enum Foo { + Bar { + bin: String, + }, + Baz, +} +"#, + r#" +enum Foo { + Bar { + bin: String, + }, + Baz, +} + +impl Clone for Foo { + $0fn clone(&self) -> Self { + match self { + Self::Bar { bin } => Self::Bar { bin: bin.clone() }, + Self::Baz => Self::Baz, + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_ord_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: ord, derive +#[derive(Partial$0Ord)] +struct Foo { + bin: usize, +} +"#, + r#" +struct Foo { + bin: usize, +} + +impl PartialOrd for Foo { + $0fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + self.bin.partial_cmp(&other.bin) + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_ord_record_struct_multi_field() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: ord, derive +#[derive(Partial$0Ord)] +struct Foo { + bin: usize, + bar: usize, + baz: usize, +} +"#, + r#" +struct Foo { + bin: usize, + bar: usize, + baz: usize, +} + +impl PartialOrd for Foo { + $0fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + match self.bin.partial_cmp(&other.bin) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + match self.bar.partial_cmp(&other.bar) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + self.baz.partial_cmp(&other.baz) + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_ord_tuple_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: ord, derive +#[derive(Partial$0Ord)] +struct Foo(usize, usize, usize); +"#, + r#" +struct Foo(usize, usize, usize); + +impl PartialOrd for Foo { + $0fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + match self.0.partial_cmp(&other.0) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + match self.1.partial_cmp(&other.1) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + self.2.partial_cmp(&other.2) + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +struct Foo { + bin: usize, + bar: usize, +} +"#, + r#" +struct Foo { + bin: usize, + bar: usize, +} + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + self.bin == other.bin && self.bar == other.bar + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_tuple_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +struct Foo(usize, usize); +"#, + r#" +struct Foo(usize, usize); + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_empty_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +struct Foo; +"#, + r#" +struct Foo; + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + true + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Foo { + Bar, + Baz, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + core::mem::discriminant(self) == core::mem::discriminant(other) + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_tuple_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Foo { + Bar(String), + Baz, +} +"#, + r#" +enum Foo { + Bar(String), + Baz, +} + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bar(l0), Self::Bar(r0)) => l0 == r0, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_partial_eq_record_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: eq, derive +#[derive(Partial$0Eq)] +enum Foo { + Bar { + bin: String, + }, + Baz { + qux: String, + fez: String, + }, + Qux {}, + Bin, +} +"#, + r#" +enum Foo { + Bar { + bin: String, + }, + Baz { + qux: String, + fez: String, + }, + Qux {}, + Bin, +} + +impl PartialEq for Foo { + $0fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bar { bin: l_bin }, Self::Bar { bin: r_bin }) => l_bin == r_bin, + (Self::Baz { qux: l_qux, fez: l_fez }, Self::Baz { qux: r_qux, fez: r_fez }) => l_qux == r_qux && l_fez == r_fez, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} +"#, + ) + } + #[test] + fn add_custom_impl_all() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: derive +mod foo { + pub trait Bar { + type Qux; + const Baz: usize = 42; + const Fez: usize; + fn foo(); + fn bar() {} + } +} + +#[derive($0Bar)] +struct Foo { + bar: String, +} +"#, + r#" +mod foo { + pub trait Bar { + type Qux; + const Baz: usize = 42; + const Fez: usize; + fn foo(); + fn bar() {} + } +} + +struct Foo { + bar: String, +} + +impl foo::Bar for Foo { + $0type Qux; + + const Baz: usize = 42; + + const Fez: usize; + + fn foo() { + todo!() + } +} +"#, + ) + } + #[test] + fn add_custom_impl_for_unique_input_unknown() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: derive +#[derive(Debu$0g)] +struct Foo { + bar: String, +} + "#, + r#" +struct Foo { + bar: String, +} + +impl Debug for Foo { + $0 +} + "#, + ) + } + + #[test] + fn add_custom_impl_for_with_visibility_modifier() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: derive +#[derive(Debug$0)] +pub struct Foo { + bar: String, +} + "#, + r#" +pub struct Foo { + bar: String, +} + +impl Debug for Foo { + $0 +} + "#, + ) + } + + #[test] + fn add_custom_impl_when_multiple_inputs() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: derive +#[derive(Display, Debug$0, Serialize)] +struct Foo {} + "#, + r#" +#[derive(Display, Serialize)] +struct Foo {} + +impl Debug for Foo { + $0 +} + "#, + ) + } + + #[test] + fn add_custom_impl_default_generic_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: default, derive +#[derive(Defau$0lt)] +struct Foo<T, U> { + foo: T, + bar: U, +} +"#, + r#" +struct Foo<T, U> { + foo: T, + bar: U, +} + +impl<T, U> Default for Foo<T, U> { + $0fn default() -> Self { + Self { foo: Default::default(), bar: Default::default() } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_clone_generic_tuple_struct_with_bounds() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(Clo$0ne)] +struct Foo<T: Clone>(T, usize); +"#, + r#" +struct Foo<T: Clone>(T, usize); + +impl<T: Clone> Clone for Foo<T> { + $0fn clone(&self) -> Self { + Self(self.0.clone(), self.1.clone()) + } +} +"#, + ) + } + + #[test] + fn test_ignore_derive_macro_without_input() { + check_assist_not_applicable( + replace_derive_with_manual_impl, + r#" +//- minicore: derive +#[derive($0)] +struct Foo {} + "#, + ) + } + + #[test] + fn test_ignore_if_cursor_on_param() { + check_assist_not_applicable( + replace_derive_with_manual_impl, + r#" +//- minicore: derive, fmt +#[derive$0(Debug)] +struct Foo {} + "#, + ); + + check_assist_not_applicable( + replace_derive_with_manual_impl, + r#" +//- minicore: derive, fmt +#[derive(Debug)$0] +struct Foo {} + "#, + ) + } + + #[test] + fn test_ignore_if_not_derive() { + check_assist_not_applicable( + replace_derive_with_manual_impl, + r#" +//- minicore: derive +#[allow(non_camel_$0case_types)] +struct Foo {} + "#, + ) + } + + #[test] + fn works_at_start_of_file() { + check_assist_not_applicable( + replace_derive_with_manual_impl, + r#" +//- minicore: derive, fmt +$0#[derive(Debug)] +struct S; + "#, + ); + } + + #[test] + fn add_custom_impl_keep_path() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone, derive +#[derive(std::fmt::Debug, Clo$0ne)] +pub struct Foo; +"#, + r#" +#[derive(std::fmt::Debug)] +pub struct Foo; + +impl Clone for Foo { + $0fn clone(&self) -> Self { + Self { } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_replace_path() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt, derive +#[derive(core::fmt::Deb$0ug, Clone)] +pub struct Foo; +"#, + r#" +#[derive(Clone)] +pub struct Foo; + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Foo").finish() + } +} +"#, + ) + } +} |