From 2144399a4ce20617a916cc284549d78f5d60ba8b Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 02:43:44 +0300 Subject: [PATCH] all: add operator overloading for `[]` (fixes #23177) --- doc/docs.md | 20 +++++ vlib/v/ast/ast.v | 29 ++++--- vlib/v/ast/str.v | 2 +- vlib/v/ast/table.v | 15 +++- vlib/v/checker/assign.v | 30 ++++++- vlib/v/checker/checker.v | 62 ++++++++++--- vlib/v/checker/fn.v | 51 +++++++++++ vlib/v/checker/infix.v | 12 +++ ...operator_assignment_without_setter_err.out | 6 ++ ..._operator_assignment_without_setter_err.vv | 10 +++ vlib/v/fmt/tests/operator_overload_keep.vv | 13 +++ vlib/v/gen/c/assign.v | 31 +++++++ vlib/v/gen/c/fn.v | 5 +- vlib/v/gen/c/index.v | 75 ++++++++++++++++ vlib/v/gen/js/fn.v | 4 +- vlib/v/gen/js/js.v | 87 +++++++++++++++++++ vlib/v/parser/fn.v | 22 +++++ .../structs/operator_overloading_index_test.v | 27 ++++++ vlib/v/util/util.v | 2 + 19 files changed, 468 insertions(+), 35 deletions(-) create mode 100644 vlib/v/checker/tests/index_operator_assignment_without_setter_err.out create mode 100644 vlib/v/checker/tests/index_operator_assignment_without_setter_err.vv create mode 100644 vlib/v/tests/structs/operator_overloading_index_test.v diff --git a/doc/docs.md b/doc/docs.md index 6a2d9f42c..85404f743 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -7453,6 +7453,23 @@ fn main() { Operator overloading is possible for the following binary operators: `+, -, *, **, /, %, <, ==`. +Indexing can be overloaded too: + +```v +struct Buffer { +mut: + data []int +} + +fn (b Buffer) [] (index int) int { + return b.data[index] +} + +fn (mut b Buffer) []= (index int, value int) { + b.data[index] = value +} +``` + ### Implicitly generated overloads - `==` is automatically generated by the compiler, but can be overridden. @@ -7473,6 +7490,9 @@ To improve safety and maintainability, operator overloading is limited. - Both arguments must have the same type (just like with all operators in V). - Overloaded operators have to return the same type as the argument (the exceptions are `<` and `==`). +- `[]` and `[]=` overloads are only allowed on structs and type aliases. +- `[]` must take exactly one index argument and return a value. +- `[]=` must take exactly an index and a value, use a `mut` receiver, and return nothing. #### Other restrictions diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 2960f0bc8..cdbfce5da 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1316,18 +1316,21 @@ pub struct IndexExpr { pub: pos token.Pos pub mut: - index Expr // [0], RangeExpr [start..end] or map[key] - or_expr OrExpr - left Expr - left_type Type // array, map, fixed array - is_setter bool - is_map bool - is_array bool - is_farray bool // fixed array - is_option bool // IfGuard - is_direct bool // Set if the underlying memory can be safely accessed - is_gated bool // #[] gated array - typ Type + index Expr // [0], RangeExpr [start..end] or map[key] + or_expr OrExpr + left Expr + left_type Type // array, map, fixed array, or overloaded index receiver + index_type Type + setter_arg_type Type + is_setter bool + is_map bool + is_array bool + is_farray bool // fixed array + is_index_operator bool // lowered as `[]` / `[]=` method calls + is_option bool // IfGuard + is_direct bool // Set if the underlying memory can be safely accessed + is_gated bool // #[] gated array + typ Type } @[minify] @@ -2577,7 +2580,7 @@ pub fn (expr Expr) is_constant() bool { pub fn (expr Expr) is_lvalue() bool { return match expr { Ident, CTempVar { true } - IndexExpr { expr.left.is_lvalue() } + IndexExpr { !expr.is_index_operator && expr.left.is_lvalue() } SelectorExpr { expr.expr.is_lvalue() } ParExpr { expr.expr.is_lvalue() } // for var := &{...(*pointer_var)} PrefixExpr { expr.right.is_lvalue() } diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index f7c1dfc2f..b84dd48ac 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -138,7 +138,7 @@ pub fn (t &Table) stringify_fn_decl(node &FnDecl, cur_mod string, m2a map[string name = name.after('__static__') } f.write_string(name) - if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] { + if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<=', '[]', '[]='] { f.write_string(' ') } t.stringify_fn_after_name(node, mut f, cur_mod, m2a) diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index ee7e883fb..d5fc49cb9 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -3026,11 +3026,18 @@ fn (mut t Table) convert_generic_default_expr(expr Expr, generic_names []string, IndexExpr { return Expr(IndexExpr{ ...expr - index: t.convert_generic_default_expr(expr.index, generic_names, concrete_types) - left: t.convert_generic_default_expr(expr.left, generic_names, concrete_types) - left_type: t.convert_generic_expr_type(expr.left_type, generic_names, + index: t.convert_generic_default_expr(expr.index, generic_names, + concrete_types) + left: t.convert_generic_default_expr(expr.left, generic_names, + concrete_types) + left_type: t.convert_generic_expr_type(expr.left_type, generic_names, + concrete_types) + index_type: t.convert_generic_expr_type(expr.index_type, generic_names, + concrete_types) + setter_arg_type: t.convert_generic_expr_type(expr.setter_arg_type, generic_names, + concrete_types) + typ: t.convert_generic_expr_type(expr.typ, generic_names, concrete_types) - typ: t.convert_generic_expr_type(expr.typ, generic_names, concrete_types) }) } InfixExpr { diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 9fb3ce499..d35915a4c 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -589,7 +589,10 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } ast.SelectorExpr { if mut left.expr is ast.IndexExpr { - if left.expr.is_map { + if left.expr.is_index_operator { + c.error('cannot assign through overloaded index expressions, use `[]=` instead', + left.pos) + } else if left.expr.is_map { left.expr.is_setter = true } } @@ -626,6 +629,21 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } } } + if mut left is ast.IndexExpr { + if left.is_index_operator && node.op != .decl_assign { + receiver_name := c.table.sym(c.unwrap_generic(left.left_type)).name + if left.setter_arg_type == 0 { + c.error('index assignment requires a `[]=` overload on type `${receiver_name}`', + left.pos) + } else if !left.left.is_lvalue() { + c.error('cannot assign through overloaded index on a non-lvalue receiver', + left.pos) + } + if node.op == .assign { + left_type = left.setter_arg_type + } + } + } left_type_unwrapped := c.unwrap_generic(ast.mktyp(left_type)) right_type_unwrapped := c.unwrap_generic(right_type) if right_type_unwrapped == 0 { @@ -961,6 +979,11 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', c.error('operator `${extracted_op}` must return `${left_name}` to be used as an assignment operator', node.pos) } + if right_sym.kind in [.alias, .struct] + && !c.check_same_type_ignoring_pointers(left_type_unwrapped, right_type_unwrapped) { + c.error('cannot assign to `${left}`: expected `${left_name}`, not `${right_name}`', + right.pos()) + } } else { if method := parent_sym.find_method_with_generic_parent(extracted_op) { c.mark_fn_decl_as_referenced(method.fkey()) @@ -969,6 +992,11 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', c.error('operator `${extracted_op}` must return `${left_name}` to be used as an assignment operator', node.pos) } + if right_sym.kind in [.alias, .struct] + && !c.check_same_type_ignoring_pointers(left_type_unwrapped, right_type_unwrapped) { + c.error('cannot assign to `${left}`: expected `${left_name}`, not `${right_name}`', + right.pos()) + } } else { if !parent_sym.is_primitive() { if left_name == right_name { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 58c23c1c6..e30477f8d 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -7592,8 +7592,9 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { c.error('unknown type for expression `${node.left}`', node.pos) return typ } - mut typ_sym := c.table.final_sym(typ) node.left_type = typ + receiver_sym := c.table.sym(c.unwrap_generic(typ)) + mut typ_sym := c.table.final_sym(typ) match typ_sym.kind { .map { node.is_map = true @@ -7618,18 +7619,6 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { } else {} } - is_aggregate_arr := typ_sym.kind == .aggregate - && (typ_sym.info as ast.Aggregate).types.filter(c.table.type_kind(it) !in [.array, .array_fixed, .string, .map]).len == 0 - if typ_sym.kind !in [.array, .array_fixed, .string, .map] - && (!typ.is_ptr() || typ_sym.kind in [.sum_type, .interface]) - && typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic) - && !is_aggregate_arr { - c.error('type `${typ_sym.name}` does not support indexing', node.pos) - } - if is_aggregate_arr { - // treating indexexpr of sumtype of array types - typ = (typ_sym.info as ast.Aggregate).types[0] - } if typ.has_flag(.option) { left_pos := node.left.pos() if node.left is ast.Ident && node.left.or_expr.kind == .absent { @@ -7646,6 +7635,53 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { c.error('type `!${typ_sym.name}` is a Result, it does not support indexing', node.left.pos()) } + if receiver_sym.kind in [.struct, .alias, .generic_inst] { + if _ := c.table.find_method(receiver_sym, '[]') { + if node.index is ast.RangeExpr { + c.error('range expressions are not supported for overloaded index operators', + node.pos) + return ast.void_type + } + if node.or_expr.kind != .absent { + c.error('custom error handling on overloaded index expressions is not supported yet', + node.or_expr.pos) + } + if node.is_gated { + c.error('`#[]` negative indexing is not supported for overloaded index operators', + node.pos) + } + method := c.table.find_method(receiver_sym, '[]') or { ast.Fn{} } + c.mark_fn_decl_as_referenced(method.fkey()) + old_expected_type := c.expected_type + c.expected_type = method.params[1].typ + index_type := c.expr(mut node.index) + c.expected_type = old_expected_type + node.index_type = index_type + c.check_expected(index_type, method.params[1].typ) or { + c.error('cannot use `${c.table.type_to_str(index_type)}` as `${c.table.type_to_str(method.params[1].typ)}` in argument 1 to `${receiver_sym.name}[]`', + node.index.pos()) + return ast.void_type + } + if setter := c.table.find_method(receiver_sym, '[]=') { + node.setter_arg_type = setter.params[2].typ + } + node.is_index_operator = true + node.typ = method.return_type + return node.typ + } + } + is_aggregate_arr := typ_sym.kind == .aggregate + && (typ_sym.info as ast.Aggregate).types.filter(c.table.type_kind(it) !in [.array, .array_fixed, .string, .map]).len == 0 + if typ_sym.kind !in [.array, .array_fixed, .string, .map] + && (!typ.is_ptr() || typ_sym.kind in [.sum_type, .interface]) + && typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic) + && !is_aggregate_arr { + c.error('type `${typ_sym.name}` does not support indexing', node.pos) + } + if is_aggregate_arr { + // treating indexexpr of sumtype of array types + typ = (typ_sym.info as ast.Aggregate).types[0] + } if typ_sym.kind == .string && !typ.is_ptr() && node.is_setter { c.error('cannot assign to s[i] since V strings are immutable\n' + '(note, that variables may be mutable but string values are always immutable, like in Go and Java)', diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index f6997bec6..e604d7a11 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -770,6 +770,57 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } } } + if node.language == .v && node.is_method && node.name == '[]' { + if node.params.len != 2 { + c.error('index operator methods should have exactly 1 argument', node.pos) + } else { + receiver_type := node.receiver.typ + receiver_sym := c.table.sym(receiver_type) + index_sym := c.table.sym(node.params[1].typ) + if index_sym.kind == .placeholder { + c.error('unknown type `${index_sym.name}`', node.params[1].type_pos) + } + if receiver_sym.kind !in [.struct, .alias] { + c.error('index operator methods are only allowed for struct and type alias', + node.pos) + } else if node.rec_mut { + c.error('receiver cannot be `mut` for `[]`, use `[]=` for writable indexing', + node.receiver_pos) + } else if node.params[1].is_mut { + c.error('argument cannot be `mut` for operator overloading', node.pos) + } else if node.return_type == ast.void_type { + c.error('index operator methods should return a value', node.return_type_pos) + } + } + } + if node.language == .v && node.is_method && node.name == '[]=' { + if node.params.len != 3 { + c.error('index assignment operator methods should have exactly 2 arguments', + node.pos) + } else { + receiver_sym := c.table.sym(node.receiver.typ) + index_sym := c.table.sym(node.params[1].typ) + value_sym := c.table.sym(node.params[2].typ) + if index_sym.kind == .placeholder { + c.error('unknown type `${index_sym.name}`', node.params[1].type_pos) + } + if value_sym.kind == .placeholder { + c.error('unknown type `${value_sym.name}`', node.params[2].type_pos) + } + if receiver_sym.kind !in [.struct, .alias] { + c.error('index assignment operator methods are only allowed for struct and type alias', + node.pos) + } else if !node.rec_mut { + c.error('receiver must be `mut` for `[]=` operator overloading', + node.receiver_pos) + } else if node.params[1].is_mut || node.params[2].is_mut { + c.error('arguments cannot be `mut` for operator overloading', node.pos) + } else if node.return_type != ast.void_type { + c.error('index assignment operator methods cannot return a value', + node.return_type_pos) + } + } + } } // TODO: c.pref.is_vet if c.file.is_test && (!node.is_method && (node.short_name.starts_with('test_') diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index 4fc6907bc..ff2f9627c 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -666,6 +666,18 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } } } + exact_left_sym := c.table.sym(unwrapped_left_type) + exact_right_sym := c.table.sym(unwrapped_right_type) + has_operator_overload := exact_left_sym.has_method_with_generic_parent(op_str) + || left_final_sym.has_method_with_generic_parent(op_str) + || exact_right_sym.has_method_with_generic_parent(op_str) + || right_final_sym.has_method_with_generic_parent(op_str) + if has_operator_overload && exact_left_sym.kind in [.alias, .struct] + && exact_right_sym.kind in [.alias, .struct] + && !c.check_same_type_ignoring_pointers(unwrapped_left_type, unwrapped_right_type) { + c.error('infix expr: cannot use `${exact_right_sym.name}` (right expression) as `${exact_left_sym.name}`', + left_right_pos) + } return_sym := c.table.sym(return_type) if return_sym.info !is ast.Alias { return_type = promoted_type diff --git a/vlib/v/checker/tests/index_operator_assignment_without_setter_err.out b/vlib/v/checker/tests/index_operator_assignment_without_setter_err.out new file mode 100644 index 000000000..9e6bc5bf8 --- /dev/null +++ b/vlib/v/checker/tests/index_operator_assignment_without_setter_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/index_operator_assignment_without_setter_err.vv:9:9: error: index assignment requires a `[]=` overload on type `Numbers` + 7 | fn main() { + 8 | mut numbers := Numbers{} + 9 | numbers[0] = 1 + | ~~~ + 10 | } diff --git a/vlib/v/checker/tests/index_operator_assignment_without_setter_err.vv b/vlib/v/checker/tests/index_operator_assignment_without_setter_err.vv new file mode 100644 index 000000000..50ade6cc7 --- /dev/null +++ b/vlib/v/checker/tests/index_operator_assignment_without_setter_err.vv @@ -0,0 +1,10 @@ +struct Numbers {} + +fn (n Numbers) [] (index int) int { + return index +} + +fn main() { + mut numbers := Numbers{} + numbers[0] = 1 +} diff --git a/vlib/v/fmt/tests/operator_overload_keep.vv b/vlib/v/fmt/tests/operator_overload_keep.vv index 381224a36..635b4e009 100644 --- a/vlib/v/fmt/tests/operator_overload_keep.vv +++ b/vlib/v/fmt/tests/operator_overload_keep.vv @@ -9,3 +9,16 @@ fn (a Foo) + (b Foo) Foo { fn (a Foo) % (b Foo) Foo { return Foo{a.x % b.x} } + +struct Indexed { +mut: + values []int +} + +fn (i Indexed) [] (index int) int { + return i.values[index] +} + +fn (mut i Indexed) []= (index int, value int) { + i.values[index] = value +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 0757aa710..05d6525e3 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -1427,6 +1427,30 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } else { '' } + if mut left is ast.IndexExpr && left.is_index_operator { + g.write(cur_line) + if node.op == .assign { + g.index_operator_call(left.left, left.left_type, left.index, left.index_type, + '[]=', val, val_type) + } else { + infix_op := token.assign_op_to_infix_op(node.op) + op_expr := ast.InfixExpr{ + left: ast.Expr(left) + right: val + op: infix_op + pos: node.pos + left_type: left.typ + right_type: val_type + promoted_type: g.type_resolver.promote_type(left.typ, val_type) + } + g.index_operator_call(left.left, left.left_type, left.index, left.index_type, + '[]=', ast.Expr(op_expr), left.typ) + } + if !g.inside_for_c_stmt { + g.writeln(';') + } + continue + } mut str_add := false mut op_overloaded := false mut op_expected_left := ast.no_type @@ -2330,6 +2354,13 @@ fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) { } } ast.IndexExpr { + if left.is_index_operator { + styp := g.styp(node.left_types[i]) + g.write('${styp} _var_${left.pos.pos} = ') + g.expr(ast.Expr(left)) + g.writeln(';') + continue + } mut container_type := left.left_type if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { resolved_container_type := g.resolved_expr_type(left.left, left.left_type) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 3250e0a25..2ddf03b98 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -602,6 +602,9 @@ fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool { if node.is_c_extern { return true } + if node.is_method && node.name in ['[]', '[]='] { + return true + } if node.mod == 'builtin' && node.name in ['print', 'println', 'eprint', 'eprintln'] { return true } @@ -1336,7 +1339,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { fn (mut g Gen) c_fn_name(node &ast.FnDecl) string { mut name := node.name - if name in ['+', '-', '*', '**', '/', '%', '<', '=='] { + if name in ['+', '-', '*', '**', '/', '%', '<', '==', '[]', '[]='] { name = util.replace_op(name) } if node.is_method { diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index 6744d1825..3d01b6953 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -6,10 +6,85 @@ module c import v.ast import v.util +struct IndexOperatorMethodInfo { + method ast.Fn + name string + receiver_type ast.Type +} + +fn (mut g Gen) resolved_index_operator_receiver_type(receiver ast.Expr, receiver_type ast.Type) ast.Type { + mut resolved_type := g.recheck_concrete_type(g.resolved_expr_type(receiver, receiver_type)) + if resolved_type == 0 { + resolved_type = receiver_type + } + if resolved_type == 0 || resolved_type.has_flag(.generic) + || g.type_has_unresolved_generic_parts(resolved_type) { + resolved_type = g.resolved_expr_type(receiver, receiver_type) + } + if resolved_type == 0 { + resolved_type = receiver_type + } + return g.unwrap_generic(g.recheck_concrete_type(resolved_type)) +} + +fn (mut g Gen) index_operator_method_info(receiver ast.Expr, receiver_type ast.Type, op string) ?IndexOperatorMethodInfo { + resolved_receiver_type := g.resolved_index_operator_receiver_type(receiver, receiver_type) + recv := + g.unwrap(if resolved_receiver_type != 0 { resolved_receiver_type } else { receiver_type }) + mut method := ast.Fn{} + mut method_name := '' + if recv.sym.has_method(op) || recv.sym.has_method_with_generic_parent(op) { + method = recv.sym.find_method_with_generic_parent(op) or { + recv.sym.find_method(op) or { return none } + } + method_name = recv.sym.cname + '_' + util.replace_op(op) + if recv.sym.is_builtin() { + method_name = 'builtin__${method_name}' + } + } else if recv.unaliased_sym.has_method_with_generic_parent(op) { + method = recv.unaliased_sym.find_method_with_generic_parent(op) or { return none } + method_name = recv.unaliased_sym.cname + '_' + util.replace_op(op) + if recv.unaliased_sym.is_builtin() { + method_name = 'builtin__${method_name}' + } + } else { + return none + } + method_name = g.specialized_method_name_from_receiver(method, recv.typ, method_name) + return IndexOperatorMethodInfo{ + method: method + name: method_name + receiver_type: recv.typ + } +} + +fn (mut g Gen) index_operator_call(receiver ast.Expr, receiver_type ast.Type, index ast.Expr, index_type ast.Type, op string, value ast.Expr, value_type ast.Type) { + info := g.index_operator_method_info(receiver, receiver_type, op) or { + g.error('missing `${op}` overload for `${g.table.type_to_str(receiver_type)}`', + receiver.pos()) + return + } + g.write(info.name) + g.write('(') + g.op_arg(receiver, info.method.params[0].typ, info.receiver_type) + g.write(', ') + g.op_arg(index, info.method.params[1].typ, index_type) + if op == '[]=' { + g.write(', ') + g.op_arg(value, info.method.params[2].typ, value_type) + } + g.write(')') +} + fn (mut g Gen) index_expr(node ast.IndexExpr) { if node.index is ast.RangeExpr { g.index_range_expr(node, node.index) } else { + if node.is_index_operator { + g.index_operator_call(node.left, node.left_type, node.index, node.index_type, '[]', + ast.empty_expr, ast.void_type) + return + } mut left_type := ast.Type(0) if node.left is ast.Ident { resolved_current_type := g.resolve_current_fn_generic_param_type(node.left.name) diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index 2d4cbe0b1..105680aed 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -626,7 +626,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { g.table.cur_fn = &it } mut name := it.name - if name in ['+', '-', '*', '**', '/', '%', '<', '=='] { + if name in ['+', '-', '*', '**', '/', '%', '<', '==', '[]', '[]='] { name = util.replace_op(name) } @@ -799,7 +799,7 @@ fn (mut g JsGen) gen_anon_fn(mut fun ast.AnonFn) { g.table.cur_fn = &it } mut name := it.name - if name in ['+', '-', '*', '**', '/', '%', '<', '=='] { + if name in ['+', '-', '*', '**', '/', '%', '<', '==', '[]', '[]='] { name = util.replace_op(name) } g.writeln('(function () { ') diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 3f2a8fb43..8783ca134 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1345,6 +1345,33 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { if stmt.op == .decl_assign { g.write(if g.inside_loop || is_mut { 'let ' } else { 'const ' }) } + if left is ast.IndexExpr && left.is_index_operator { + if stmt.op == .assign { + g.index_operator_call(left.left, left.left_type, left.index, left.index_type, + '[]=', val, stmt.right_types[i]) + } else { + infix_op := token.assign_op_to_infix_op(stmt.op) + op_expr := ast.InfixExpr{ + left: ast.Expr(left) + right: val + op: infix_op + pos: stmt.pos + left_type: left.typ + right_type: stmt.right_types[i] + promoted_type: left.typ + } + g.index_operator_call(left.left, left.left_type, left.index, left.index_type, + '[]=', ast.Expr(op_expr), left.typ) + } + if semicolon { + if g.inside_loop { + g.write('; ') + } else { + g.writeln(';') + } + } + continue + } mut array_set := false mut map_set := false @@ -3023,6 +3050,11 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { } fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { + if expr.is_index_operator { + g.index_operator_call(expr.left, expr.left_type, expr.index, expr.index_type, '[]', + ast.empty_expr, ast.void_type) + return + } left_sym := g.table.sym(expr.left_type) // TODO: Handle splice setting if it's implemented if expr.index is ast.RangeExpr { @@ -3900,6 +3932,8 @@ fn replace_op(s string) string { '**' { '_pow' } '/' { '_div' } '%' { '_mod' } + '[]' { '_index' } + '[]=' { '_index_set' } '<' { '_lt' } '>' { '_gt' } '==' { '_eq' } @@ -3907,6 +3941,59 @@ fn replace_op(s string) string { } } +struct JsIndexOperatorMethodInfo { + method ast.Fn + name string + receiver_type ast.Type +} + +fn (mut g JsGen) resolved_index_operator_receiver_type(receiver ast.Expr, receiver_type ast.Type) ast.Type { + _ = receiver + return g.unwrap_generic(receiver_type) +} + +fn (mut g JsGen) index_operator_method_info(receiver ast.Expr, receiver_type ast.Type, op string) ?JsIndexOperatorMethodInfo { + resolved_receiver_type := g.resolved_index_operator_receiver_type(receiver, receiver_type) + receiver_info := g.unwrap(if resolved_receiver_type != 0 { + resolved_receiver_type + } else { + receiver_type + }) + mut method := ast.Fn{} + if receiver_info.sym.has_method(op) || receiver_info.sym.has_method_with_generic_parent(op) { + method = receiver_info.sym.find_method_with_generic_parent(op) or { + receiver_info.sym.find_method(op) or { return none } + } + } else if receiver_info.unaliased_sym.has_method_with_generic_parent(op) { + method = receiver_info.unaliased_sym.find_method_with_generic_parent(op) or { return none } + } else { + return none + } + method_name := g.styp(receiver_info.unaliased.set_nr_muls(0)) + '_' + util.replace_op(op) + return JsIndexOperatorMethodInfo{ + method: method + name: method_name + receiver_type: receiver_info.typ + } +} + +fn (mut g JsGen) index_operator_call(receiver ast.Expr, receiver_type ast.Type, index ast.Expr, index_type ast.Type, op string, value ast.Expr, value_type ast.Type) { + info := g.index_operator_method_info(receiver, receiver_type, op) or { + verror('missing `${op}` overload for `${g.table.type_to_str(receiver_type)}`') + return + } + g.write(info.name) + g.write('(') + g.op_arg(receiver, info.method.params[0].typ, info.receiver_type) + g.write(', ') + g.op_arg(index, info.method.params[1].typ, index_type) + if op == '[]=' { + g.write(', ') + g.op_arg(value, info.method.params[2].typ, value_type) + } + g.write(')') +} + fn (mut g JsGen) gen_postfix_index_expr(expr ast.IndexExpr, op token.Kind) { left_sym := g.table.sym(expr.left_type) left_sym_kind := left_sym.kind diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 130756977..163cfa905 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -738,6 +738,28 @@ fn (mut p Parser) fn_decl() ast.FnDecl { p.error_with_pos('cannot duplicate operator overload `${name}`', p.tok.pos()) } p.next() + } else if p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr && p.peek_token(2).kind == .lpar { + name = '[]' + if rec.typ == ast.void_type { + p.error_with_pos('cannot use operator overloading with normal functions', p.tok.pos()) + } + if type_sym.has_method(name) { + p.error_with_pos('cannot duplicate operator overload `${name}`', p.tok.pos()) + } + p.next() + p.next() + } else if p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr && p.peek_token(2).kind == .assign + && p.peek_token(3).kind == .lpar { + name = '[]=' + if rec.typ == ast.void_type { + p.error_with_pos('cannot use operator overloading with normal functions', p.tok.pos()) + } + if type_sym.has_method(name) { + p.error_with_pos('cannot duplicate operator overload `${name}`', p.tok.pos()) + } + p.next() + p.next() + p.next() } else if p.tok.kind in [.ne, .gt, .ge, .le] && p.peek_tok.kind == .lpar { p.error_with_pos('cannot overload `!=`, `>`, `<=` and `>=` as they are auto generated from `==` and`<`', p.tok.pos()) diff --git a/vlib/v/tests/structs/operator_overloading_index_test.v b/vlib/v/tests/structs/operator_overloading_index_test.v new file mode 100644 index 000000000..af83c76fe --- /dev/null +++ b/vlib/v/tests/structs/operator_overloading_index_test.v @@ -0,0 +1,27 @@ +module main + +struct IntList { +mut: + values []int +} + +fn (l IntList) [] (index int) int { + return l.values[index] +} + +fn (mut l IntList) []= (index int, value int) { + l.values[index] = value +} + +fn test_operator_overloading_index() { + mut list := IntList{ + values: [3, 5, 8] + } + assert list[1] == 5 + list[1] = 33 + assert list[1] == 33 + list[1] += 7 + assert list[1] == 40 + list[0] *= 4 + assert list[0] == 12 +} diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index 601a88693..7e6cc479c 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -435,6 +435,8 @@ pub fn replace_op(s string) string { '**' { '_pow' } '/' { '_div' } '%' { '_mod' } + '[]' { '_index' } + '[]=' { '_index_set' } '<' { '_lt' } '>' { '_gt' } '==' { '_eq' } -- 2.39.5