Skip to content

Commit c5942c5

Browse files
Merge #9835
9835: feat: Generate default trait fn impl when generating `Hash`. r=Veykril a=yoshuawuyts Implements a default trait function body when generating the `Hash` trait for a type. Thanks! r? `@Veykril` Co-authored-by: Yoshua Wuyts <yoshuawuyts@gmail.com>
2 parents e652545 + 4b5139e commit c5942c5

File tree

4 files changed

+151
-2
lines changed

4 files changed

+151
-2
lines changed

crates/ide/src/hover.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,8 +2732,8 @@ fn foo() {
27322732
file_id: FileId(
27332733
1,
27342734
),
2735-
full_range: 252..434,
2736-
focus_range: 291..297,
2735+
full_range: 253..435,
2736+
focus_range: 292..298,
27372737
name: "Future",
27382738
kind: Trait,
27392739
description: "pub trait Future",

crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,83 @@ impl Default for Foo {
366366
Self { }
367367
}
368368
}
369+
"#,
370+
)
371+
}
372+
373+
#[test]
374+
fn add_custom_impl_hash_record_struct() {
375+
check_assist(
376+
replace_derive_with_manual_impl,
377+
r#"
378+
//- minicore: hash
379+
#[derive(Has$0h)]
380+
struct Foo {
381+
bin: usize,
382+
bar: usize,
383+
}
384+
"#,
385+
r#"
386+
struct Foo {
387+
bin: usize,
388+
bar: usize,
389+
}
390+
391+
impl core::hash::Hash for Foo {
392+
$0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
393+
self.bin.hash(state);
394+
self.bar.hash(state);
395+
}
396+
}
397+
"#,
398+
)
399+
}
400+
401+
#[test]
402+
fn add_custom_impl_hash_tuple_struct() {
403+
check_assist(
404+
replace_derive_with_manual_impl,
405+
r#"
406+
//- minicore: hash
407+
#[derive(Has$0h)]
408+
struct Foo(usize, usize);
409+
"#,
410+
r#"
411+
struct Foo(usize, usize);
412+
413+
impl core::hash::Hash for Foo {
414+
$0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
415+
self.0.hash(state);
416+
self.1.hash(state);
417+
}
418+
}
419+
"#,
420+
)
421+
}
422+
423+
#[test]
424+
fn add_custom_impl_hash_enum() {
425+
check_assist(
426+
replace_derive_with_manual_impl,
427+
r#"
428+
//- minicore: hash
429+
#[derive(Has$0h)]
430+
enum Foo {
431+
Bar,
432+
Baz,
433+
}
434+
"#,
435+
r#"
436+
enum Foo {
437+
Bar,
438+
Baz,
439+
}
440+
441+
impl core::hash::Hash for Foo {
442+
$0fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
443+
core::mem::discriminant(self).hash(state);
444+
}
445+
}
369446
"#,
370447
)
371448
}

crates/ide_assists/src/utils/gen_trait_fn_body.rs

Lines changed: 61 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,63 @@ 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+
fn gen_hash_call(target: ast::Expr) -> ast::Stmt {
159+
let method = make::name_ref("hash");
160+
let arg = make::expr_path(make::ext::ident_path("state"));
161+
let expr = make::expr_method_call(target, method, make::arg_list(Some(arg)));
162+
let stmt = make::expr_stmt(expr);
163+
stmt.into()
164+
}
165+
166+
let body = match adt {
167+
// `Hash` cannot be derived for unions, so no default impl can be provided.
168+
ast::Adt::Union(_) => return None,
169+
170+
// => std::mem::discriminant(self).hash(state);
171+
ast::Adt::Enum(_) => {
172+
let root = make::ext::ident_path("core");
173+
let submodule = make::ext::ident_path("mem");
174+
let fn_name = make::ext::ident_path("discriminant");
175+
let fn_name = make::path_concat(submodule, fn_name);
176+
let fn_name = make::expr_path(make::path_concat(root, fn_name));
177+
178+
let arg = make::expr_path(make::ext::ident_path("self"));
179+
let fn_call = make::expr_call(fn_name, make::arg_list(Some(arg)));
180+
let stmt = gen_hash_call(fn_call);
181+
182+
make::block_expr(Some(stmt), None).indent(ast::edit::IndentLevel(1))
183+
}
184+
ast::Adt::Struct(strukt) => match strukt.field_list() {
185+
// => self.<field>.hash(state);*
186+
Some(ast::FieldList::RecordFieldList(field_list)) => {
187+
let mut stmts = vec![];
188+
for field in field_list.fields() {
189+
let base = make::expr_path(make::ext::ident_path("self"));
190+
let target = make::expr_field(base, &field.name()?.to_string());
191+
stmts.push(gen_hash_call(target));
192+
}
193+
make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1))
194+
}
195+
196+
// => self.<field_index>.hash(state);*
197+
Some(ast::FieldList::TupleFieldList(field_list)) => {
198+
let mut stmts = vec![];
199+
for (i, _) in field_list.fields().enumerate() {
200+
let base = make::expr_path(make::ext::ident_path("self"));
201+
let target = make::expr_field(base, &format!("{}", i));
202+
stmts.push(gen_hash_call(target));
203+
}
204+
make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1))
205+
}
206+
207+
// No fields in the body means there's nothing to hash.
208+
None => return None,
209+
},
210+
};
211+
212+
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
213+
Some(())
214+
}

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)