From 7d7a50daf7114fc8af62d74cf3162132dd826eaa Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Tue, 10 Aug 2021 13:20:24 +0200 Subject: [PATCH 1/4] Add clone generation tests --- .../replace_derive_with_manual_impl.rs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) 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..4281e3349597 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,117 @@ 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_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, + Baz, +} +"#, + r#" +enum Foo { + Bar(String), + Baz, +} + +impl Clone for Foo { + $0fn clone(&self) -> Self { + match self { + Self::Bar(arg1) => Self::Bar(arg1.clone()), + Self::Baz => Self::Baz, + } + } +} "#, ) } From 7ddc26aea1b2f73bf6bd3cfa3ecce8ce05068cf1 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Tue, 10 Aug 2021 13:55:26 +0200 Subject: [PATCH 2/4] add clone generation for structs and bare enums --- .../replace_derive_with_manual_impl.rs | 28 +++++-- .../src/utils/gen_trait_fn_body.rs | 78 ++++++++++++++++++- 2 files changed, 97 insertions(+), 9 deletions(-) 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 4281e3349597..10959bfd01c5 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 @@ -467,10 +467,7 @@ struct Foo { impl Clone for Foo { $0fn clone(&self) -> Self { - Self { - bin: self.bin.clone(), - bar: self.bar.clone(), - } + Self { bin: self.bin.clone(), bar: self.bar.clone() } } } "#, @@ -498,6 +495,27 @@ impl Clone for Foo { ) } + #[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( @@ -536,7 +554,7 @@ impl Clone for Foo { //- minicore: clone #[derive(Clo$0ne)] enum Foo { - Bar, + Bar(String), 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..ee7d594c03f3 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,75 @@ 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); + + 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 +158,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 +252,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 +263,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() { From e0adb39de3ea58edf054d7344f4daf694a8910ae Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Tue, 10 Aug 2021 14:43:20 +0200 Subject: [PATCH 3/4] gen clone for record enums --- .../replace_derive_with_manual_impl.rs | 34 +++++++++++++++++ .../src/utils/gen_trait_fn_body.rs | 37 +++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) 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 10959bfd01c5..65495e3b8f65 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 @@ -572,6 +572,40 @@ impl Clone for Foo { } } } +"#, + ) + } + + #[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 ee7d594c03f3..2f4eb6ca55c3 100644 --- a/crates/ide_assists/src/utils/gen_trait_fn_body.rs +++ b/crates/ide_assists/src/utils/gen_trait_fn_body.rs @@ -42,9 +42,40 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { let right = make::ext::ident_path(&format!("{}", name)); let variant_name = make::path_concat(left, right); - 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)); + 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 pattern = 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(pattern.into()), None, record_expr)); + } + + // => match self { Self::Name(arg1) => Self::Name(arg1.clone()) } + Some(ast::FieldList::TupleFieldList(list)) => todo!(), + + // => 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")); From 2f866974d9bfd7cd3be89f3806dbb623cf358bf3 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Tue, 10 Aug 2021 15:07:13 +0200 Subject: [PATCH 4/4] gen clone for tuple enums --- .../replace_derive_with_manual_impl.rs | 2 +- .../src/utils/gen_trait_fn_body.rs | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) 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 65495e3b8f65..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 @@ -567,7 +567,7 @@ enum Foo { impl Clone for Foo { $0fn clone(&self) -> Self { match self { - Self::Bar(arg1) => Self::Bar(arg1.clone()), + Self::Bar(arg0) => Self::Bar(arg0.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 2f4eb6ca55c3..04f396d46e20 100644 --- a/crates/ide_assists/src/utils/gen_trait_fn_body.rs +++ b/crates/ide_assists/src/utils/gen_trait_fn_body.rs @@ -58,16 +58,29 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { let field = make::record_expr_field(name_ref, Some(method_call)); fields.push(field); } - let pattern = make::record_pat(variant_name.clone(), pats.into_iter()); - + 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(pattern.into()), None, record_expr)); + 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)) => todo!(), + 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 => {