diff --git a/vlib/builtin/map.v b/vlib/builtin/map.v index 7fb9ab6511..1b4ae23253 100644 --- a/vlib/builtin/map.v +++ b/vlib/builtin/map.v @@ -149,13 +149,13 @@ fn (mut d DenseArray) expand() int { return push_index } -type MapHashFn = fn (voidptr) u64 +type MapHashFn = fn (new_key voidptr) u64 -type MapEqFn = fn (voidptr, voidptr) bool +type MapEqFn = fn (new_key voidptr, existing_map_key voidptr) bool -type MapCloneFn = fn (voidptr, voidptr) +type MapCloneFn = fn (existing_map_key voidptr, new_key voidptr) -type MapFreeFn = fn (voidptr) +type MapFreeFn = fn (existing_map_key voidptr) // map is the internal representation of a V `map` type. pub struct map { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ecf8982611..bccfce4ea8 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3073,12 +3073,61 @@ fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { g.autofree_scope_stmts << af.str() } +fn (mut g Gen) map_check_presence(struct_name string, c_fn_name string, v_method_name string) { + if c_fn_name == '' { + verror('struct `${struct_name}`, used as map key, should implement a `${v_method_name}` method with a reference receiver') + } +} + fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) { mut hash_fn := '' mut key_eq_fn := '' mut clone_fn := '' mut free_fn := '&map_free_nop' match key_typ.kind { + .struct_ { + for method in key_typ.methods { + if method.receiver_type.nr_muls() == 0 { + continue + } + match method.name { + 'map_hash' { + if method.return_type != ast.u64_type || method.params.len != 1 { + eprintln('the map_hash method of struct ${key_typ.name} should return `u64`, and should have no parameters') + continue + } + hash_fn = '&${key_typ.cname}_map_hash' + } + 'map_eq' { + if method.return_type != ast.bool_type || method.params.len != 2 { + eprintln('the map_eq method of struct ${key_typ.name} should return `bool`, and should have exactly 1 parameter of type `&{${key_typ.name}}`') + continue + } + key_eq_fn = '&${key_typ.cname}_map_eq' + } + 'map_clone' { + if method.return_type != ast.void_type || method.params.len != 2 { + eprintln('the map_clone method of struct ${key_typ.name} should have exactly 1 parameter of type `&{${key_typ.name}}` and its receiver should be `mut x ${key_typ.name}`, and should not return anything') + continue + } + clone_fn = '&${key_typ.cname}_map_clone' + } + 'map_free' { + if method.return_type != ast.void_type || method.params.len != 1 { + eprintln('the map_free method of struct ${key_typ.name} should have no parameters, and should not return anything') + continue + } + free_fn = '&${key_typ.cname}_map_free' + } + else {} + } + } + g.map_check_presence(key_typ.name, hash_fn, 'map_hash') + g.map_check_presence(key_typ.name, key_eq_fn, 'map_eq') + g.map_check_presence(key_typ.name, clone_fn, 'map_clone') + g.map_check_presence(key_typ.name, free_fn, 'map_free') + return hash_fn, key_eq_fn, clone_fn, free_fn + } .alias { alias_key_type := (key_typ.info as ast.Alias).parent_type return g.map_fn_ptrs(g.table.sym(alias_key_type)) diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index b346606bb7..2f00cf0ef0 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -117,7 +117,7 @@ pub fn (mut p Parser) parse_map_type() ast.Type { key_sym := p.table.sym(key_type) is_alias := key_sym.kind == .alias key_type_supported := key_type in [ast.string_type_idx, ast.voidptr_type_idx] - || key_sym.kind in [.enum_, .placeholder, .any] + || key_sym.kind in [.enum_, .placeholder, .any, .struct_] || ((key_type.is_int() || key_type.is_float() || is_alias) && !key_type.is_ptr()) if !key_type_supported { if is_alias { @@ -125,7 +125,7 @@ pub fn (mut p Parser) parse_map_type() ast.Type { return 0 } s := p.table.type_to_str(key_type) - p.error_with_pos('maps only support string, integer, float, rune, enum or voidptr keys for now (not `${s}`)', + p.error_with_pos('maps only support string, integer, float, rune, enum, struct or voidptr keys for now (not `${s}`)', p.tok.pos()) return 0 } diff --git a/vlib/v/tests/map_struct_as_key_test.v b/vlib/v/tests/map_struct_as_key_test.v new file mode 100644 index 0000000000..f1d4d38104 --- /dev/null +++ b/vlib/v/tests/map_struct_as_key_test.v @@ -0,0 +1,41 @@ +struct Abc { + x int + y int +} + +fn (s &Abc) map_hash() u64 { + // eprintln('${@METHOD} called, s: $s') + return u64(s.x) << 32 + u64(s.y) +} + +fn (s &Abc) map_eq(b &Abc) bool { + // eprintln('${@METHOD} called, s: $s') + return s.x == b.x && s.y == b.y +} + +fn (mut s Abc) map_clone(new &Abc) { + // eprintln('${@METHOD} called, s: $s | new: $new') + unsafe { + *s = *new + } +} + +fn (s &Abc) map_free() { + eprintln('${@METHOD} called, s: ${s}') +} + +fn test_map_with_struct_as_key() { + s0 := Abc{} + s1 := Abc{1, 2} + mut m := map[Abc]int{} + m[s0] = 123 + m[s1] = 456 + assert m[s0] == 123 + assert m[s1] == 456 + dump(m) + m[s1] = 789 + dump(m[s1]) + dump(m) + assert m[s0] == 123 + assert m[s1] == 789 +}