From ec2535e9cef8a60c9ef19a50ac928c4b5d4b87bf Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Tue, 10 Aug 2021 11:59:41 +0200 Subject: [PATCH 1/2] impl gen hash for enums --- .../replace_derive_with_manual_impl.rs | 27 +++++++++++ .../src/utils/gen_trait_fn_body.rs | 47 +++++++++++++++++++ crates/test_utils/src/minicore.rs | 11 +++++ 3 files changed, 85 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 2add705db2e..1cd9ab222c5 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 @@ -366,6 +366,33 @@ impl Default for Foo { Self { } } } +"#, + ) + } + + #[test] + fn add_custom_impl_hash_enum() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: hash +#[derive(Has$0h)] +enum Foo { + Bar, + Baz, +} +"#, + r#" +enum Foo { + Bar, + Baz, +} + +impl core::hash::Hash for Foo { + $0fn hash(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); + } +} "#, ) } 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 17e006a7553..2908f62dd30 100644 --- a/crates/ide_assists/src/utils/gen_trait_fn_body.rs +++ b/crates/ide_assists/src/utils/gen_trait_fn_body.rs @@ -18,6 +18,7 @@ pub(crate) fn gen_trait_fn_body( match trait_path.segment()?.name_ref()?.text().as_str() { "Debug" => gen_debug_impl(adt, func), "Default" => gen_default_impl(adt, func), + "Hash" => gen_hash_impl(adt, func), _ => None, } } @@ -151,3 +152,49 @@ fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { } } } + +/// Generate a `Hash` impl based on the fields and members of the target type. +fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { + let body = match adt { + // `Hash` cannot be derived for unions, so no default impl can be provided. + ast::Adt::Union(_) => return None, + + // => std::mem::discriminant(self).hash(state); + ast::Adt::Enum(_) => { + let root = make::ext::ident_path("core"); + let submodule = make::ext::ident_path("mem"); + let fn_name = make::ext::ident_path("discriminant"); + let fn_name = make::path_concat(submodule, fn_name); + let fn_name = make::expr_path(make::path_concat(root, fn_name)); + + let arg = make::expr_path(make::ext::ident_path("self")); + let fn_call = make::expr_call(fn_name, make::arg_list(Some(arg))); + + let method = make::name_ref("hash"); + let arg = make::expr_path(make::ext::ident_path("state")); + let expr = make::expr_method_call(fn_call, method, make::arg_list(Some(arg))); + let stmt = make::expr_stmt(expr); + + make::block_expr(Some(stmt.into()), None).indent(ast::edit::IndentLevel(1)) + } + ast::Adt::Struct(strukt) => match strukt.field_list() { + // => self..hash(state);* + Some(ast::FieldList::RecordFieldList(field_list)) => { + // let mut stmts = vec![]; + for field in field_list.fields() {} + todo!(); + } + + // => self..hash(state);* + Some(ast::FieldList::TupleFieldList(field_list)) => { + todo!(); + } + + // No fields in the body means there's nothing to hash. + None => make::ext::empty_block_expr(), + }, + }; + + ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); + Some(()) +} diff --git a/crates/test_utils/src/minicore.rs b/crates/test_utils/src/minicore.rs index c37a0aa1efb..fcc1a169202 100644 --- a/crates/test_utils/src/minicore.rs +++ b/crates/test_utils/src/minicore.rs @@ -25,6 +25,7 @@ //! iterator: option //! iterators: iterator, fn //! default: sized +//! hash: //! clone: sized //! copy: clone //! from: sized @@ -87,6 +88,16 @@ pub mod default { } // endregion:default +// region:hash +pub mod hash { + pub trait Hasher {} + + pub trait Hash { + fn hash(&self, state: &mut H); + } +} +// endregion:hash + // region:clone pub mod clone { #[lang = "clone"] From 4b5139e8a5e3ccc0bc960fa2322f61b0e905f235 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Tue, 10 Aug 2021 12:13:30 +0200 Subject: [PATCH 2/2] impl gen hash for structs --- crates/ide/src/hover.rs | 4 +- .../replace_derive_with_manual_impl.rs | 50 +++++++++++++++++++ .../src/utils/gen_trait_fn_body.rs | 36 +++++++++---- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index a9e2b526394..3a17035ae85 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -2732,8 +2732,8 @@ fn foo() { file_id: FileId( 1, ), - full_range: 252..434, - focus_range: 291..297, + full_range: 253..435, + focus_range: 292..298, name: "Future", kind: Trait, description: "pub trait Future", 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 1cd9ab222c5..bd0b2028a1a 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 @@ -370,6 +370,56 @@ impl Default for Foo { ) } + #[test] + fn add_custom_impl_hash_record_struct() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: hash +#[derive(Has$0h)] +struct Foo { + bin: usize, + bar: usize, +} +"#, + r#" +struct Foo { + bin: usize, + bar: usize, +} + +impl core::hash::Hash for Foo { + $0fn hash(&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(Has$0h)] +struct Foo(usize, usize); +"#, + r#" +struct Foo(usize, usize); + +impl core::hash::Hash for Foo { + $0fn hash(&self, state: &mut H) { + self.0.hash(state); + self.1.hash(state); + } +} +"#, + ) + } + #[test] fn add_custom_impl_hash_enum() { check_assist( 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 2908f62dd30..9ed8cbdbc73 100644 --- a/crates/ide_assists/src/utils/gen_trait_fn_body.rs +++ b/crates/ide_assists/src/utils/gen_trait_fn_body.rs @@ -155,6 +155,14 @@ fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { /// Generate a `Hash` impl based on the fields and members of the target type. fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { + fn gen_hash_call(target: ast::Expr) -> ast::Stmt { + let method = make::name_ref("hash"); + let arg = make::expr_path(make::ext::ident_path("state")); + let expr = make::expr_method_call(target, method, make::arg_list(Some(arg))); + let stmt = make::expr_stmt(expr); + stmt.into() + } + let body = match adt { // `Hash` cannot be derived for unions, so no default impl can be provided. ast::Adt::Union(_) => return None, @@ -169,29 +177,35 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { let arg = make::expr_path(make::ext::ident_path("self")); let fn_call = make::expr_call(fn_name, make::arg_list(Some(arg))); + let stmt = gen_hash_call(fn_call); - let method = make::name_ref("hash"); - let arg = make::expr_path(make::ext::ident_path("state")); - let expr = make::expr_method_call(fn_call, method, make::arg_list(Some(arg))); - let stmt = make::expr_stmt(expr); - - make::block_expr(Some(stmt.into()), None).indent(ast::edit::IndentLevel(1)) + make::block_expr(Some(stmt), None).indent(ast::edit::IndentLevel(1)) } ast::Adt::Struct(strukt) => match strukt.field_list() { // => self..hash(state);* Some(ast::FieldList::RecordFieldList(field_list)) => { - // let mut stmts = vec![]; - for field in field_list.fields() {} - todo!(); + let mut stmts = 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()); + stmts.push(gen_hash_call(target)); + } + make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) } // => self..hash(state);* Some(ast::FieldList::TupleFieldList(field_list)) => { - todo!(); + let mut stmts = vec![]; + for (i, _) in field_list.fields().enumerate() { + let base = make::expr_path(make::ext::ident_path("self")); + let target = make::expr_field(base, &format!("{}", i)); + stmts.push(gen_hash_call(target)); + } + make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) } // No fields in the body means there's nothing to hash. - None => make::ext::empty_block_expr(), + None => return None, }, };