From 30829e366dbcb913d9a499ac041367c0edac9802 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 01:51:08 +0300 Subject: [PATCH] cgen: add support for array as a map key (fixes #22992) --- doc/docs.md | 3 +- vlib/v/ast/table.v | 44 +-- vlib/v/gen/c/assign.v | 6 +- vlib/v/gen/c/auto_str_methods.v | 3 + vlib/v/gen/c/cgen.v | 272 ++++++++++++++---- vlib/v/gen/c/for.v | 7 +- vlib/v/gen/c/index.v | 17 +- vlib/v/gen/c/infix.v | 10 +- vlib/v/parser/parse_type.v | 7 +- .../builtin_maps/map_fixed_array_keys_test.v | 25 ++ 10 files changed, 302 insertions(+), 92 deletions(-) create mode 100644 vlib/v/tests/builtin_maps/map_fixed_array_keys_test.v diff --git a/doc/docs.md b/doc/docs.md index e29b442ef..0bbbe9b23 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -1491,7 +1491,8 @@ println(m.keys()) // ['one', 'two'] m.delete('two') ``` -Maps can have keys of type string, rune, integer, float or voidptr. +Maps can have keys of type string, rune, integer, float, voidptr, +enum, or fixed arrays of those supported key types. The whole map can be initialized using this short syntax: diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 506bd8a12..ee7e883fb 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1325,26 +1325,36 @@ pub fn (t &Table) supports_map_key_type(typ Type) bool { if typ == 0 || typ.has_flag(.generic) { return true } - mut current_typ := typ.clear_flags() - for { - if current_typ.nr_muls() > 0 { - return false + mut seen := map[int]bool{} + return t.supports_map_key_type_in_type(typ.clear_flags(), mut seen) +} + +fn (t &Table) supports_map_key_type_in_type(typ Type, mut seen map[int]bool) bool { + current_typ := typ.clear_flags() + if current_typ.nr_muls() > 0 { + return false + } + type_idx := current_typ.idx() + if seen[type_idx] { + return true + } + seen[type_idx] = true + sym := t.sym(current_typ) + match sym.kind { + .alias { + return t.supports_map_key_type_in_type((sym.info as Alias).parent_type, mut seen) } - sym := t.sym(current_typ) - match sym.kind { - .alias { - current_typ = (sym.info as Alias).parent_type.clear_flags() - } - .u8, .i8, .char, .i16, .u16, .enum, .int, .i32, .u32, .rune, .f32, .voidptr, .u64, - .i64, .f64, .string { - return true - } - else { - return false - } + .array_fixed { + return t.supports_map_key_type_in_type((sym.info as ArrayFixed).elem_type, mut seen) + } + .u8, .i8, .char, .i16, .u16, .enum, .int, .i32, .u32, .rune, .f32, .voidptr, .u64, .i64, + .f64, .string { + return true + } + else { + return false } } - return false } // array_source_name generates the original name for the v source. diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 37eb4ce1e..0757aa710 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -2392,7 +2392,6 @@ fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) { g.writeln(';') } else if sym.kind == .map { info := sym.info as ast.Map - skeytyp := g.styp(info.key_type) styp := g.styp(info.value_type) zero := g.type_default(info.value_type) val_sym := g.table.sym(info.value_type) @@ -2411,9 +2410,8 @@ fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) { } else { g.expr(left.left) } - g.write(', &(${skeytyp}[]){') - g.expr(left.index) - g.write('}') + g.write(', ') + g.write_map_key_arg(left.index, info.key_type) if val_sym.kind == .function { g.writeln(', &(voidptr[]){ ${zero} });') } else { diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 0cbe3bf4b..4fce6384e 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -886,6 +886,9 @@ fn (mut g Gen) gen_str_for_map(info ast.Map, styp string, str_fn_name string) { if key_sym.kind == .string { g.auto_str_funcs.writeln('\t\tstring key = *(string*)builtin__DenseArray_key(&m.key_values, i);') + } else if key_sym.kind == .array_fixed { + g.auto_str_funcs.writeln('\t\t${key_styp} key;') + g.auto_str_funcs.writeln('\t\tmemcpy(key, builtin__DenseArray_key(&m.key_values, i), sizeof(${key_styp}));') } else { g.auto_str_funcs.writeln('\t\t${key_styp} key = *(${key_styp}*)builtin__DenseArray_key(&m.key_values, i);') } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index bc58dd2d1..a71ef5922 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -202,6 +202,8 @@ mut: waiter_fns shared []string // functions that wait for `go xxx()` to finish needed_equality_fns []ast.Type generated_eq_fns []ast.Type + needed_map_key_fns []ast.Type + generated_map_key_fns map[ast.Type]bool generated_array_interface_cast_fns shared map[string]bool generated_array_interface_repeat_fns shared map[string]bool array_sort_fn shared []string @@ -339,61 +341,62 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO } mut reflection_strings := map[string]int{} mut global_g := Gen{ - fid: -1 - tid: v_gettid().hex() - file: unsafe { nil } - out: strings.new_builder(512000) - cheaders: strings.new_builder(15000) - includes: strings.new_builder(100) - preincludes: strings.new_builder(100) - postincludes: strings.new_builder(100) - typedefs: strings.new_builder(100) - enum_typedefs: strings.new_builder(100) - type_definitions: strings.new_builder(100) - sort_fn_definitions: strings.new_builder(100) - alias_definitions: strings.new_builder(100) - hotcode_definitions: strings.new_builder(100) - channel_definitions: strings.new_builder(100) - thread_definitions: strings.new_builder(100) - comptime_definitions: strings.new_builder(100) - definitions: strings.new_builder(100) - gowrappers: strings.new_builder(100) - auto_str_funcs: strings.new_builder(100) - dump_funcs: strings.new_builder(100) - pcs_declarations: strings.new_builder(100) - cov_declarations: strings.new_builder(100) - embedded_data: strings.new_builder(1000) - out_options_forward: strings.new_builder(100) - out_options: strings.new_builder(100) - out_results_forward: strings.new_builder(100) - out_results: strings.new_builder(100) - shared_types: strings.new_builder(100) - shared_functions: strings.new_builder(100) - json_forward_decls: strings.new_builder(100) - sql_buf: strings.new_builder(100) - table: table - pref: pref_ - fn_decl: unsafe { nil } - anon_fn: unsafe { nil } - is_autofree: pref_.autofree - indent: -1 - module_built: module_built - timers_should_print: timers_should_print - timers: util.new_timers( + fid: -1 + tid: v_gettid().hex() + file: unsafe { nil } + out: strings.new_builder(512000) + cheaders: strings.new_builder(15000) + includes: strings.new_builder(100) + preincludes: strings.new_builder(100) + postincludes: strings.new_builder(100) + typedefs: strings.new_builder(100) + enum_typedefs: strings.new_builder(100) + type_definitions: strings.new_builder(100) + sort_fn_definitions: strings.new_builder(100) + alias_definitions: strings.new_builder(100) + hotcode_definitions: strings.new_builder(100) + channel_definitions: strings.new_builder(100) + thread_definitions: strings.new_builder(100) + comptime_definitions: strings.new_builder(100) + definitions: strings.new_builder(100) + gowrappers: strings.new_builder(100) + auto_str_funcs: strings.new_builder(100) + dump_funcs: strings.new_builder(100) + pcs_declarations: strings.new_builder(100) + cov_declarations: strings.new_builder(100) + embedded_data: strings.new_builder(1000) + out_options_forward: strings.new_builder(100) + out_options: strings.new_builder(100) + out_results_forward: strings.new_builder(100) + out_results: strings.new_builder(100) + shared_types: strings.new_builder(100) + shared_functions: strings.new_builder(100) + json_forward_decls: strings.new_builder(100) + sql_buf: strings.new_builder(100) + table: table + pref: pref_ + fn_decl: unsafe { nil } + anon_fn: unsafe { nil } + is_autofree: pref_.autofree + indent: -1 + module_built: module_built + timers_should_print: timers_should_print + timers: util.new_timers( should_print: timers_should_print label: 'global_cgen' ) - inner_loop: unsafe { &ast.empty_stmt } - field_data_type: table.find_type('FieldData') - enum_data_type: table.find_type('EnumData') - variant_data_type: table.find_type('VariantData') - is_cc_msvc: pref_.ccompiler == 'msvc' - use_segfault_handler: pref_.should_use_segfault_handler() - static_modifier: if pref_.parallel_cc || pref_.is_o { 'static ' } else { '' } - static_non_parallel: if !pref_.parallel_cc { 'static ' } else { '' } - has_reflection: 'v.reflection' in table.modules - has_debugger: 'v.debug' in table.modules - reflection_strings: &reflection_strings + inner_loop: unsafe { &ast.empty_stmt } + field_data_type: table.find_type('FieldData') + enum_data_type: table.find_type('EnumData') + variant_data_type: table.find_type('VariantData') + is_cc_msvc: pref_.ccompiler == 'msvc' + use_segfault_handler: pref_.should_use_segfault_handler() + static_modifier: if pref_.parallel_cc || pref_.is_o { 'static ' } else { '' } + static_non_parallel: if !pref_.parallel_cc { 'static ' } else { '' } + has_reflection: 'v.reflection' in table.modules + has_debugger: 'v.debug' in table.modules + reflection_strings: &reflection_strings + generated_map_key_fns: map[ast.Type]bool{} } global_g.type_resolver = type_resolver.TypeResolver.new(table, global_g) @@ -507,6 +510,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.auto_fn_definitions << g.auto_fn_definitions global_g.anon_fn_definitions << g.anon_fn_definitions global_g.needed_equality_fns << g.needed_equality_fns // duplicates are resolved later in gen_equality_fns + global_g.needed_map_key_fns << g.needed_map_key_fns global_g.array_contains_types << g.array_contains_types global_g.array_index_types << g.array_index_types global_g.array_last_index_types << g.array_last_index_types @@ -567,6 +571,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.gen_array_index_methods(true) // .last_index() global_g.gen_array_get_methods() global_g.gen_equality_fns() + global_g.gen_map_key_fns() global_g.gen_free_methods() global_g.register_iface_return_types() global_g.write_results() @@ -1005,6 +1010,7 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) voidptr has_reflection: 'v.reflection' in global_g.table.modules has_debugger: 'v.debug' in global_g.table.modules reflection_strings: global_g.reflection_strings + generated_map_key_fns: map[ast.Type]bool{} } g.type_resolver = type_resolver.TypeResolver.new(global_g.table, g) g.comptime = &g.type_resolver.info @@ -5048,6 +5054,10 @@ fn (mut g Gen) map_fn_ptrs(key_sym ast.TypeSymbol) (string, string, string, stri clone_fn = '&builtin__map_clone_string' free_fn = '&builtin__map_free_string' } + .array_fixed { + key_type := ast.new_type(key_sym.idx) + return g.fixed_array_map_fn_ptrs(key_type) + } else { verror('map key type `${key_sym.name}` not supported') } @@ -5055,6 +5065,164 @@ fn (mut g Gen) map_fn_ptrs(key_sym ast.TypeSymbol) (string, string, string, stri return hash_fn, key_eq_fn, clone_fn, free_fn } +@[inline] +fn (mut g Gen) map_key_fn_key(typ ast.Type) ast.Type { + return g.table.fully_unaliased_type(g.unwrap_generic(typ).set_nr_muls(0)) +} + +@[inline] +fn (mut g Gen) fixed_array_map_fn_base(typ ast.Type) string { + return '${g.styp(g.map_key_fn_key(typ))}_map_key' +} + +fn (mut g Gen) fixed_array_map_fn_ptrs(key_type ast.Type) (string, string, string, string) { + key := g.map_key_fn_key(key_type) + g.needed_map_key_fns << key + base := g.fixed_array_map_fn_base(key) + return '&${base}_hash', '&${base}_eq', '&${base}_clone', '&${base}_free' +} + +fn (mut g Gen) gen_map_key_fns() { + for needed_typ in g.needed_map_key_fns { + key := g.map_key_fn_key(needed_typ) + if g.generated_map_key_fns[key] { + continue + } + sym := g.table.sym(key) + match sym.kind { + .array_fixed { + g.gen_fixed_array_map_key_fns(key) + } + else { + verror('could not generate map key functions for type `${sym.name}`') + } + } + } +} + +fn (mut g Gen) gen_fixed_array_map_key_fns(key_type ast.Type) { + key := g.map_key_fn_key(key_type) + if g.generated_map_key_fns[key] { + return + } + g.generated_map_key_fns[key] = true + key_sym := g.table.sym(key) + if key_sym.kind != .array_fixed { + verror('map key type `${key_sym.name}` is not a fixed array') + } + key_info := key_sym.info as ast.ArrayFixed + key_styp := g.styp(key) + base := g.fixed_array_map_fn_base(key) + hash_fn := '${base}_hash' + eq_fn := '${base}_eq' + clone_fn := '${base}_clone' + free_fn := '${base}_free' + array_eq_fn := g.gen_fixed_array_equality_fn(key) + g.definitions.writeln('${g.static_non_parallel}u64 ${hash_fn}(voidptr pkey);') + g.definitions.writeln('${g.static_non_parallel}bool ${eq_fn}(voidptr a, voidptr b);') + g.definitions.writeln('${g.static_non_parallel}void ${clone_fn}(voidptr dest, voidptr pkey);') + g.definitions.writeln('${g.static_non_parallel}void ${free_fn}(voidptr pkey);') + + mut hash_builder := strings.new_builder(256) + hash_builder.writeln('${g.static_non_parallel}u64 ${hash_fn}(voidptr pkey) {') + hash_builder.writeln('\t${key_styp}* key = (${key_styp}*)pkey;') + hash_builder.writeln('\tu64 hash = 0;') + hash_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < ${key_info.size}; ++i) {') + hash_builder.writeln('\t\thash = wyhash64(hash, ${g.fixed_array_map_key_hash_expr(key_info.elem_type, + '(*key)[i]')});') + hash_builder.writeln('\t}') + hash_builder.writeln('\treturn hash;') + hash_builder.writeln('}') + g.auto_fn_definitions << hash_builder.str() + + mut eq_builder := strings.new_builder(128) + eq_builder.writeln('${g.static_non_parallel}bool ${eq_fn}(voidptr a, voidptr b) {') + eq_builder.writeln('\treturn ${array_eq_fn}_arr_eq(*(${key_styp}*)a, *(${key_styp}*)b);') + eq_builder.writeln('}') + g.auto_fn_definitions << eq_builder.str() + + mut clone_builder := strings.new_builder(256) + clone_builder.writeln('${g.static_non_parallel}void ${clone_fn}(voidptr dest, voidptr pkey) {') + clone_builder.writeln('\t${key_styp}* dest_key = (${key_styp}*)dest;') + clone_builder.writeln('\t${key_styp}* src_key = (${key_styp}*)pkey;') + clone_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < ${key_info.size}; ++i) {') + g.fixed_array_map_key_clone_stmt(mut clone_builder, key_info.elem_type, '(*dest_key)[i]', + '(*src_key)[i]') + clone_builder.writeln('\t}') + clone_builder.writeln('}') + g.auto_fn_definitions << clone_builder.str() + + mut free_builder := strings.new_builder(256) + free_builder.writeln('${g.static_non_parallel}void ${free_fn}(voidptr pkey) {') + free_builder.writeln('\t${key_styp}* key = (${key_styp}*)pkey;') + free_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < ${key_info.size}; ++i) {') + g.fixed_array_map_key_free_stmt(mut free_builder, key_info.elem_type, '(*key)[i]') + free_builder.writeln('\t}') + free_builder.writeln('}') + g.auto_fn_definitions << free_builder.str() +} + +fn (mut g Gen) fixed_array_map_key_hash_expr(elem_type ast.Type, expr string) string { + elem := g.map_key_fn_key(elem_type) + elem_sym := g.table.final_sym(elem) + match elem_sym.kind { + .string { + return 'builtin__map_hash_string(&${expr})' + } + .array_fixed { + g.gen_fixed_array_map_key_fns(elem) + return '${g.fixed_array_map_fn_base(elem)}_hash(&${expr})' + } + else { + return 'wyhash((const void*)(&${expr}), sizeof(${g.styp(elem)}), 0, _wyp)' + } + } +} + +fn (mut g Gen) fixed_array_map_key_clone_stmt(mut sb strings.Builder, elem_type ast.Type, dest string, + src string) { + elem := g.map_key_fn_key(elem_type) + elem_sym := g.table.final_sym(elem) + match elem_sym.kind { + .string { + sb.writeln('\t\tbuiltin__map_clone_string(&${dest}, &${src});') + } + .array_fixed { + g.gen_fixed_array_map_key_fns(elem) + sb.writeln('\t\t${g.fixed_array_map_fn_base(elem)}_clone(&${dest}, &${src});') + } + else { + sb.writeln('\t\tmemcpy(&${dest}, &${src}, sizeof(${g.styp(elem)}));') + } + } +} + +fn (mut g Gen) fixed_array_map_key_free_stmt(mut sb strings.Builder, elem_type ast.Type, expr string) { + elem := g.map_key_fn_key(elem_type) + elem_sym := g.table.final_sym(elem) + match elem_sym.kind { + .string { + sb.writeln('\t\tbuiltin__map_free_string(&${expr});') + } + .array_fixed { + g.gen_fixed_array_map_key_fns(elem) + sb.writeln('\t\t${g.fixed_array_map_fn_base(elem)}_free(&${expr});') + } + else {} + } +} + +fn (mut g Gen) write_map_key_arg(expr ast.Expr, key_type ast.Type) { + key_styp := g.styp(key_type) + if g.table.final_sym(key_type).kind == .array_fixed && expr is ast.Ident { + g.expr(expr) + return + } + g.write('&(${key_styp}[]){') + g.expr(expr) + g.write('}') +} + fn (mut g Gen) expr(node_ ast.Expr) { old_discard_or_result := g.discard_or_result old_is_void_expr_stmt := g.is_void_expr_stmt diff --git a/vlib/v/gen/c/for.v b/vlib/v/gen/c/for.v index cd7beb20f..dac5822cd 100644 --- a/vlib/v/gen/c/for.v +++ b/vlib/v/gen/c/for.v @@ -737,7 +737,12 @@ fn (mut g Gen) for_in_stmt(node_ ast.ForInStmt) { if node.key_var != '_' { key_styp := g.styp(node.key_type) key := c_name(node.key_var) - g.writeln('${key_styp} ${key} = *(${key_styp}*)builtin__DenseArray_key(&${cond_var}${dot_or_ptr}key_values, ${idx});') + if g.table.final_sym(node.key_type).kind == .array_fixed { + g.writeln('${key_styp} ${key};') + g.writeln('memcpy(${key}, builtin__DenseArray_key(&${cond_var}${dot_or_ptr}key_values, ${idx}), sizeof(${key_styp}));') + } else { + g.writeln('${key_styp} ${key} = *(${key_styp}*)builtin__DenseArray_key(&${cond_var}${dot_or_ptr}key_values, ${idx});') + } // TODO: analyze whether node.key_type has a .clone() method and call .clone() for all types: if node.key_type == ast.string_type { g.writeln('${key} = builtin__string_clone(${key});') diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index a6a1aa606..6744d1825 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -576,7 +576,6 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { val_type = candidate_val_type } } - key_type_str := g.styp(key_type) val_sym := g.table.final_sym(val_type) left_is_shared := map_left_type.has_flag(.shared_f) val_type_str := if val_sym.kind == .function { @@ -615,15 +614,14 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { if left_is_shared { g.write('->val') } - g.write(', &(${key_type_str}[]){') + g.write(', ') old_is_arraymap_set := g.is_arraymap_set old_is_assign_lhs := g.is_assign_lhs g.is_arraymap_set = false g.is_assign_lhs = false - g.expr(node.index) + g.write_map_key_arg(node.index, key_type) g.is_arraymap_set = old_is_arraymap_set g.is_assign_lhs = old_is_assign_lhs - g.write('}') g.arraymap_set_pos = g.out.len g.write(', &(${val_type_str}[]) { ') if g.assign_op != .assign && val_type != ast.string_type { @@ -646,12 +644,12 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { if left_is_shared { g.write('->val') } - g.write(', &(${key_type_str}[]){') + g.write(', ') old_is_assign_lhs := g.is_assign_lhs g.is_assign_lhs = false - g.expr(node.index) + g.write_map_key_arg(node.index, key_type) g.is_assign_lhs = old_is_assign_lhs - g.write('}, &(${val_type_str}[]){ ${zero} }))') + g.write(', &(${val_type_str}[]){ ${zero} }))') } else { zero := g.type_default(val_type) is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result @@ -694,9 +692,8 @@ fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { g.write('.val') } } - g.write('), &(${key_type_str}[]){') - g.expr(node.index) - g.write('}') + g.write('), ') + g.write_map_key_arg(node.index, key_type) if gen_or { g.write('))') } else if is_fn_last_index_call { diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index aea5fba54..a20cdecce 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -861,9 +861,13 @@ fn (mut g Gen) infix_expr_in_op(node ast.InfixExpr) { } else { node.left_type }) - g.write('ADDR(${styp}, ') - g.expr(node.left) - g.write(')') + if g.table.final_sym(node.left_type).kind == .array_fixed && node.left is ast.Ident { + g.expr(node.left) + } else { + g.write('ADDR(${styp}, ') + g.expr(node.left) + g.write(')') + } } else { g.expr(node.left) } diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index dcfba6a0c..153810726 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -203,16 +203,15 @@ 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_type.is_int() || key_type.is_float() || is_alias) && !key_type.is_ptr()) + key_type_supported := p.table.supports_map_key_type(key_type) + || key_sym.kind in [.placeholder, .any] if !key_type_supported { if is_alias { p.error('cannot use the alias type as the parent type is unsupported') 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, fixed array or voidptr keys for now (not `${s}`)', p.tok.pos()) return 0 } diff --git a/vlib/v/tests/builtin_maps/map_fixed_array_keys_test.v b/vlib/v/tests/builtin_maps/map_fixed_array_keys_test.v new file mode 100644 index 000000000..34a656c75 --- /dev/null +++ b/vlib/v/tests/builtin_maps/map_fixed_array_keys_test.v @@ -0,0 +1,25 @@ +fn test_fixed_array_string_keys() { + mut m := map[[2]string]int{} + key := ['foo', 'bar']! + lookup := ['foo', 'bar']! + m[key] = 5 + assert m[key] == 5 + assert m[lookup] == 5 + assert lookup in m + assert m.len == 1 +} + +fn test_nested_fixed_array_string_keys() { + mut m := map[[2][2]string]int{} + key := [ + ['alpha', 'beta']!, + ['gamma', 'delta']!, + ]! + lookup := [ + ['alpha', 'beta']!, + ['gamma', 'delta']!, + ]! + m[key] = 9 + assert m[lookup] == 9 + assert key in m +} -- 2.39.5