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 2add705db2..1cd9ab222c 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 17e006a755..2908f62dd3 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 c37a0aa1ef..fcc1a16920 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"]