Skip to content

Commit ec2535e

Browse files
committed
impl gen hash for enums
1 parent e652545 commit ec2535e

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,33 @@ impl Default for Foo {
366366
Self { }
367367
}
368368
}
369+
"#,
370+
)
371+
}
372+
373+
#[test]
374+
fn add_custom_impl_hash_enum() {
375+
check_assist(
376+
replace_derive_with_manual_impl,
377+
r#"
378+
//- minicore: hash
379+
#[derive(Has$0h)]
380+
enum Foo {
381+
Bar,
382+
Baz,
383+
}
384+
"#,
385+
r#"
386+
enum Foo {
387+
Bar,
388+
Baz,
389+
}
390+
391+
impl core::hash::Hash for Foo {
392+
$0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
393+
core::mem::discriminant(self).hash(state);
394+
}
395+
}
369396
"#,
370397
)
371398
}

crates/ide_assists/src/utils/gen_trait_fn_body.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub(crate) fn gen_trait_fn_body(
1818
match trait_path.segment()?.name_ref()?.text().as_str() {
1919
"Debug" => gen_debug_impl(adt, func),
2020
"Default" => gen_default_impl(adt, func),
21+
"Hash" => gen_hash_impl(adt, func),
2122
_ => None,
2223
}
2324
}
@@ -151,3 +152,49 @@ fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
151152
}
152153
}
153154
}
155+
156+
/// Generate a `Hash` impl based on the fields and members of the target type.
157+
fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
158+
let body = match adt {
159+
// `Hash` cannot be derived for unions, so no default impl can be provided.
160+
ast::Adt::Union(_) => return None,
161+
162+
// => std::mem::discriminant(self).hash(state);
163+
ast::Adt::Enum(_) => {
164+
let root = make::ext::ident_path("core");
165+
let submodule = make::ext::ident_path("mem");
166+
let fn_name = make::ext::ident_path("discriminant");
167+
let fn_name = make::path_concat(submodule, fn_name);
168+
let fn_name = make::expr_path(make::path_concat(root, fn_name));
169+
170+
let arg = make::expr_path(make::ext::ident_path("self"));
171+
let fn_call = make::expr_call(fn_name, make::arg_list(Some(arg)));
172+
173+
let method = make::name_ref("hash");
174+
let arg = make::expr_path(make::ext::ident_path("state"));
175+
let expr = make::expr_method_call(fn_call, method, make::arg_list(Some(arg)));
176+
let stmt = make::expr_stmt(expr);
177+
178+
make::block_expr(Some(stmt.into()), None).indent(ast::edit::IndentLevel(1))
179+
}
180+
ast::Adt::Struct(strukt) => match strukt.field_list() {
181+
// => self.<field>.hash(state);*
182+
Some(ast::FieldList::RecordFieldList(field_list)) => {
183+
// let mut stmts = vec![];
184+
for field in field_list.fields() {}
185+
todo!();
186+
}
187+
188+
// => self.<field_index>.hash(state);*
189+
Some(ast::FieldList::TupleFieldList(field_list)) => {
190+
todo!();
191+
}
192+
193+
// No fields in the body means there's nothing to hash.
194+
None => make::ext::empty_block_expr(),
195+
},
196+
};
197+
198+
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
199+
Some(())
200+
}

crates/test_utils/src/minicore.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
//! iterator: option
2626
//! iterators: iterator, fn
2727
//! default: sized
28+
//! hash:
2829
//! clone: sized
2930
//! copy: clone
3031
//! from: sized
@@ -87,6 +88,16 @@ pub mod default {
8788
}
8889
// endregion:default
8990

91+
// region:hash
92+
pub mod hash {
93+
pub trait Hasher {}
94+
95+
pub trait Hash {
96+
fn hash<H: Hasher>(&self, state: &mut H);
97+
}
98+
}
99+
// endregion:hash
100+
90101
// region:clone
91102
pub mod clone {
92103
#[lang = "clone"]

0 commit comments

Comments
 (0)