diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index bd0b2028a1a0..f256bf035ba6 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs @@ -443,6 +443,169 @@ impl core::hash::Hash for Foo { 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(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(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(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(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(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(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, + } + } +} "#, ) } diff --git a/crates/ide_assists/src/utils/gen_trait_fn_body.rs b/crates/ide_assists/src/utils/gen_trait_fn_body.rs index 9ed8cbdbc735..04f396d46e20 100644 --- a/crates/ide_assists/src/utils/gen_trait_fn_body.rs +++ b/crates/ide_assists/src/utils/gen_trait_fn_body.rs @@ -16,6 +16,7 @@ pub(crate) fn gen_trait_fn_body( adt: &ast::Adt, ) -> Option<()> { match trait_path.segment()?.name_ref()?.text().as_str() { + "Clone" => gen_clone_impl(adt, func), "Debug" => gen_debug_impl(adt, func), "Default" => gen_default_impl(adt, func), "Hash" => gen_hash_impl(adt, func), @@ -23,6 +24,119 @@ pub(crate) fn gen_trait_fn_body( } } +/// Generate a `Clone` impl based on the fields and members of the target type. +fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { + fn gen_clone_call(target: ast::Expr) -> ast::Expr { + let method = make::name_ref("clone"); + make::expr_method_call(target, method, make::arg_list(None)) + } + let expr = match adt { + // `Clone` cannot be derived for unions, so no default impl can be provided. + ast::Adt::Union(_) => return None, + ast::Adt::Enum(enum_) => { + let list = enum_.variant_list()?; + let mut arms = vec![]; + for variant in list.variants() { + let name = variant.name()?; + let left = make::ext::ident_path("Self"); + let right = make::ext::ident_path(&format!("{}", name)); + let variant_name = make::path_concat(left, right); + + match variant.field_list() { + // => match self { Self::Name { x } => Self::Name { x: x.clone() } } + Some(ast::FieldList::RecordFieldList(list)) => { + let mut pats = vec![]; + let mut fields = vec![]; + for field in list.fields() { + let field_name = field.name()?; + let pat = make::ident_pat(false, false, field_name.clone()); + pats.push(pat.into()); + + let path = make::ext::ident_path(&field_name.to_string()); + let method_call = gen_clone_call(make::expr_path(path)); + let name_ref = make::name_ref(&field_name.to_string()); + let field = make::record_expr_field(name_ref, Some(method_call)); + fields.push(field); + } + let pat = make::record_pat(variant_name.clone(), pats.into_iter()); + let fields = make::record_expr_field_list(fields); + let record_expr = make::record_expr(variant_name, fields).into(); + arms.push(make::match_arm(Some(pat.into()), None, record_expr)); + } + + // => match self { Self::Name(arg1) => Self::Name(arg1.clone()) } + Some(ast::FieldList::TupleFieldList(list)) => { + let mut pats = vec![]; + let mut fields = vec![]; + for (i, _) in list.fields().enumerate() { + let field_name = format!("arg{}", i); + let pat = make::ident_pat(false, false, make::name(&field_name)); + pats.push(pat.into()); + + let f_path = make::expr_path(make::ext::ident_path(&field_name)); + fields.push(gen_clone_call(f_path)); + } + let pat = make::tuple_struct_pat(variant_name.clone(), pats.into_iter()); + let struct_name = make::expr_path(variant_name); + let tuple_expr = make::expr_call(struct_name, make::arg_list(fields)); + arms.push(make::match_arm(Some(pat.into()), None, tuple_expr)); + } + + // => match self { Self::Name => Self::Name } + None => { + let pattern = make::path_pat(variant_name.clone()); + let variant_expr = make::expr_path(variant_name); + arms.push(make::match_arm(Some(pattern.into()), None, variant_expr)); + } + } + } + + let match_target = make::expr_path(make::ext::ident_path("self")); + let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1)); + make::expr_match(match_target, list) + } + ast::Adt::Struct(strukt) => { + match strukt.field_list() { + // => Self { name: self.name.clone() } + Some(ast::FieldList::RecordFieldList(field_list)) => { + let mut fields = vec![]; + for field in field_list.fields() { + let base = make::expr_path(make::ext::ident_path("self")); + let target = make::expr_field(base, &field.name()?.to_string()); + let method_call = gen_clone_call(target); + let name_ref = make::name_ref(&field.name()?.to_string()); + let field = make::record_expr_field(name_ref, Some(method_call)); + fields.push(field); + } + let struct_name = make::ext::ident_path("Self"); + let fields = make::record_expr_field_list(fields); + make::record_expr(struct_name, fields).into() + } + // => Self(self.0.clone(), self.1.clone()) + Some(ast::FieldList::TupleFieldList(field_list)) => { + let mut fields = vec![]; + for (i, _) in field_list.fields().enumerate() { + let f_path = make::expr_path(make::ext::ident_path("self")); + let target = make::expr_field(f_path, &format!("{}", i)).into(); + fields.push(gen_clone_call(target)); + } + let struct_name = make::expr_path(make::ext::ident_path("Self")); + make::expr_call(struct_name, make::arg_list(fields)) + } + // => Self { } + None => { + let struct_name = make::ext::ident_path("Self"); + let fields = make::record_expr_field_list(None); + make::record_expr(struct_name, fields).into() + } + } + } + }; + let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); + ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); + Some(()) +} + /// Generate a `Debug` impl based on the fields and members of the target type. fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { let annotated_name = adt.name()?; @@ -88,10 +202,10 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { Some(ast::FieldList::TupleFieldList(field_list)) => { let method = make::name_ref("debug_tuple"); let mut expr = make::expr_method_call(target, method, args); - for (idx, _) in field_list.fields().enumerate() { + for (i, _) in field_list.fields().enumerate() { let f_path = make::expr_path(make::ext::ident_path("self")); let f_path = make::expr_ref(f_path, false); - let f_path = make::expr_field(f_path, &format!("{}", idx)).into(); + let f_path = make::expr_field(f_path, &format!("{}", i)).into(); let method = make::name_ref("field"); expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path))); } @@ -182,7 +296,7 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { make::block_expr(Some(stmt), None).indent(ast::edit::IndentLevel(1)) } ast::Adt::Struct(strukt) => match strukt.field_list() { - // => self..hash(state);* + // => self..hash(state); Some(ast::FieldList::RecordFieldList(field_list)) => { let mut stmts = vec![]; for field in field_list.fields() { @@ -193,7 +307,7 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) } - // => self..hash(state);* + // => self..hash(state); Some(ast::FieldList::TupleFieldList(field_list)) => { let mut stmts = vec![]; for (i, _) in field_list.fields().enumerate() {