From a75b9622292f36a85c5da0926c1f8a1e1623e4ae Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 02:38:59 +0300 Subject: [PATCH] all: fix Matlab/Pytorch/Numpy syntax for multidimensional array indexing and slicing (fixes #21628) --- doc/docs.md | 23 ++- vlib/builtin/builtin.v | 11 ++ vlib/v/ast/ast.v | 13 +- vlib/v/ast/str.v | 3 +- vlib/v/checker/assign.v | 7 +- vlib/v/checker/checker.v | 150 ++++++++++++++- vlib/v/comptime/comptime.v | 5 + vlib/v/fmt/fmt.v | 8 +- vlib/v/fmt/tests/array_slices_expected.vv | 9 + vlib/v/fmt/tests/array_slices_input.vv | 6 +- vlib/v/generics/generics.v | 6 + vlib/v/parser/parser.v | 178 ++++-------------- .../structs/operator_overloading_index_test.v | 76 ++++++++ vlib/v/transformer/transformer.v | 5 + 14 files changed, 344 insertions(+), 156 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index c31e75c86..d290dbe28 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -7518,6 +7518,23 @@ fn (mut b Buffer) []= (index int, value int) { } ``` +Custom types can also opt into multidimensional and slice syntax by taking +`SliceIndex` or `[]SliceIndex`: + +```v +struct Tensor {} + +fn (t Tensor) [] (parts []SliceIndex) Tensor { + return t +} + +fn main() { + t := Tensor{} + _ = t[1..3, ..] + _ = t[2, 4..8] +} +``` + ### Implicitly generated overloads - `==` is automatically generated by the compiler, but can be overridden. @@ -7539,8 +7556,10 @@ To improve safety and maintainability, operator overloading is limited. - 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. +- `[]` must take exactly one parameter and return a value. +- `[]=` must take exactly a parameter and a value, use a `mut` receiver, and return nothing. +- Use `SliceIndex` or `[]SliceIndex` for slice syntax like `tensor[1..3]` + or multidimensional syntax like `tensor[1, ..]`. #### Other restrictions diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index e0d69d770..e8a6de462 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -18,6 +18,17 @@ struct VCastTypeIndexName { // will be filled in cgen __global as_cast_type_indexes []VCastTypeIndexName +// SliceIndex describes one overloaded `[]` index or slice part. +pub struct SliceIndex { +pub: + is_range bool + value int + low int + high int + has_low bool + has_high bool +} + @[direct_array_access] fn __as_cast(obj voidptr, obj_type int, expected_type int) voidptr { if obj_type != expected_type { diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 393533b91..fc6eaa1f4 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -19,8 +19,8 @@ pub const result_name = '_result' pub const option_name = '_option' // V builtin types defined on .v files -pub const builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', option_name, - result_name] +pub const builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', 'SliceIndex', + option_name, result_name] pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl @@ -1320,7 +1320,8 @@ pub struct IndexExpr { pub: pos token.Pos pub mut: - index Expr // [0], RangeExpr [start..end] or map[key] + index Expr // [0], RangeExpr [start..end] or map[key] + indices []Expr // parsed index parts, e.g. [i], [i, j], [1..3, ..] or_expr OrExpr left Expr left_type Type // array, map, fixed array, or overloaded index receiver @@ -2904,7 +2905,11 @@ pub fn (node Node) children() []Node { IndexExpr { index_expr := node children << index_expr.left - children << index_expr.index + if index_expr.indices.len > 0 { + children << index_expr.indices.map(Node(it)) + } else { + children << index_expr.index + } } IfExpr { if_expr := node diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index d1364ce92..368094d3e 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -603,7 +603,8 @@ pub fn (x Expr) str() string { return parts.join('') } IndexExpr { - return '${x.left.str()}[${x.index.str()}]' + parts := if x.indices.len > 0 { x.indices } else { [x.index] } + return '${x.left.str()}[${parts.map(it.str()).join(', ')}]' } InfixExpr { return '${x.left.str()} ${x.op.str()} ${x.right.str()}' diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index c74ec6c22..91a0014d9 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -336,9 +336,6 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.error('cannot dereference a function call on the left side of an assignment, use a temporary variable', left.pos) } - } else if mut left is ast.IndexExpr && left.index is ast.RangeExpr { - c.error('cannot reassign using range expression on the left side of an assignment', - left.pos) } else if mut left is ast.Ident && node.op == .decl_assign { if left.name in c.global_names { c.note('the global variable named `${left.name}` already exists', left.pos) @@ -356,6 +353,10 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { c.is_index_assign = true } left_type = c.expr(mut left) + if left is ast.IndexExpr && left.index is ast.RangeExpr && !left.is_index_operator { + c.error('cannot reassign using range expression on the left side of an assignment', + left.pos) + } left_type = c.smartcasted_assign_lhs_type(left, left_type) c.is_index_assign = false c.expected_type = c.unwrap_generic(left_type) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 221802795..2b1a35a73 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -7696,6 +7696,119 @@ fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_ty } } +fn (c &Checker) index_expr_parts(node ast.IndexExpr) []ast.Expr { + return if node.indices.len > 0 { node.indices } else { [node.index] } +} + +fn (mut c Checker) is_builtin_slice_index_type(typ ast.Type) bool { + sym := c.table.final_sym(c.unwrap_generic(typ)) + return sym.mod == 'builtin' && sym.name.all_after_last('.') == 'SliceIndex' +} + +fn (mut c Checker) is_builtin_slice_index_array_type(typ ast.Type) bool { + sym := c.table.final_sym(c.unwrap_generic(typ)) + return if sym.info is ast.Array { + c.is_builtin_slice_index_type(sym.info.elem_type) + } else { + false + } +} + +fn (mut c Checker) builtin_slice_index_type() ast.Type { + mut typ := c.table.find_type('SliceIndex') + if typ == 0 { + typ = c.table.find_type('builtin.SliceIndex') + } + return typ +} + +fn (mut c Checker) slice_index_struct_init(part ast.Expr) ast.Expr { + slice_index_type := c.builtin_slice_index_type() + slice_index_name := c.table.sym(slice_index_type).name + part_pos := part.pos() + mut init_fields := []ast.StructInitField{} + match part { + ast.RangeExpr { + init_fields << ast.StructInitField{ + name: 'is_range' + name_pos: part_pos + pos: part_pos + expr: ast.BoolLiteral{ + val: true + pos: part_pos + } + } + if part.has_low { + init_fields << ast.StructInitField{ + name: 'low' + name_pos: part.low.pos() + pos: part.low.pos() + expr: part.low + } + init_fields << ast.StructInitField{ + name: 'has_low' + name_pos: part.low.pos() + pos: part.low.pos() + expr: ast.BoolLiteral{ + val: true + pos: part.low.pos() + } + } + } + if part.has_high { + init_fields << ast.StructInitField{ + name: 'high' + name_pos: part.high.pos() + pos: part.high.pos() + expr: part.high + } + init_fields << ast.StructInitField{ + name: 'has_high' + name_pos: part.high.pos() + pos: part.high.pos() + expr: ast.BoolLiteral{ + val: true + pos: part.high.pos() + } + } + } + } + else { + init_fields << ast.StructInitField{ + name: 'value' + name_pos: part_pos + pos: part_pos + expr: part + } + } + } + + return ast.Expr(ast.StructInit{ + pos: part_pos + name_pos: part_pos + typ_str: slice_index_name + typ: slice_index_type + init_fields: init_fields + }) +} + +fn (mut c Checker) slice_index_array_init(parts []ast.Expr) ast.Expr { + slice_index_type := c.builtin_slice_index_type() + array_type := ast.new_type(c.table.find_or_register_array(slice_index_type)) + pos := parts[0].pos().extend(parts.last().pos()) + mut exprs := []ast.Expr{cap: parts.len} + for part in parts { + exprs << c.slice_index_struct_init(part) + } + return ast.Expr(ast.ArrayInit{ + pos: pos + elem_type_pos: pos + exprs: exprs + elem_type: slice_index_type + typ: array_type + }) +} + fn (mut c Checker) check_map_key_type(got ast.Type, expected ast.Type) bool { if c.map_key_pointer_mismatch(got, expected) { return false @@ -7775,13 +7888,11 @@ 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()) } + raw_indices := c.index_expr_parts(node) + is_multi_index := raw_indices.len > 1 + has_slice_part := raw_indices.any(it is ast.RangeExpr) 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) @@ -7792,6 +7903,30 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { } method := c.table.find_method(receiver_sym, '[]') or { ast.Fn{} } c.mark_fn_decl_as_referenced(method.fkey()) + accepts_slice_index := c.is_builtin_slice_index_type(method.params[1].typ) + accepts_slice_index_array := c.is_builtin_slice_index_array_type(method.params[1].typ) + if is_multi_index { + if !accepts_slice_index_array { + c.error('multi-index expressions on overloaded `[]` require a `[]SliceIndex` parameter', + node.pos) + return ast.void_type + } + node.index = c.slice_index_array_init(raw_indices) + } else if has_slice_part { + if accepts_slice_index { + node.index = c.slice_index_struct_init(raw_indices[0]) + } else if accepts_slice_index_array { + node.index = c.slice_index_array_init(raw_indices) + } else { + c.error('slice expressions on overloaded `[]` require `SliceIndex` or `[]SliceIndex` parameters', + node.pos) + return ast.void_type + } + } else if accepts_slice_index { + node.index = c.slice_index_struct_init(raw_indices[0]) + } else if accepts_slice_index_array { + node.index = c.slice_index_array_init(raw_indices) + } old_expected_type := c.expected_type c.expected_type = method.params[1].typ index_type := c.expr(mut node.index) @@ -7810,6 +7945,11 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { return node.typ } } + if is_multi_index { + c.error('multi-index expressions are only supported by types with overloaded `[]` methods', + node.pos) + return ast.void_type + } 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] diff --git a/vlib/v/comptime/comptime.v b/vlib/v/comptime/comptime.v index ce90b195e..7634fe97b 100644 --- a/vlib/v/comptime/comptime.v +++ b/vlib/v/comptime/comptime.v @@ -373,6 +373,11 @@ pub fn (mut c Comptime) expr(mut node ast.Expr) ast.Expr { ast.IndexExpr { node.left = c.expr(mut node.left) node.index = c.expr(mut node.index) + mut indices := []ast.Expr{cap: node.indices.len} + for mut index in node.indices { + indices << c.expr(mut index) + } + node.indices = indices node.or_expr = c.expr(mut node.or_expr) as ast.OrExpr } ast.InfixExpr { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 3faf00e9e..f7bf3bdb9 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2635,7 +2635,13 @@ pub fn (mut f Fmt) index_expr(node ast.IndexExpr) { last_index_expr_state := f.is_index_expr f.is_index_expr = true f.write('[') - f.expr(node.index) + parts := if node.indices.len > 0 { node.indices } else { [node.index] } + for i, part in parts { + if i > 0 { + f.write(', ') + } + f.expr(part) + } f.write(']') f.is_index_expr = last_index_expr_state if node.or_expr.kind != .absent { diff --git a/vlib/v/fmt/tests/array_slices_expected.vv b/vlib/v/fmt/tests/array_slices_expected.vv index b0bcee7b0..e5d72a551 100644 --- a/vlib/v/fmt/tests/array_slices_expected.vv +++ b/vlib/v/fmt/tests/array_slices_expected.vv @@ -1,5 +1,12 @@ +struct Tensor {} + +fn (t Tensor) [] (parts []SliceIndex) string { + return '' +} + fn fn_contains_index_expr() { arr := [1, 2, 3, 4, 5] + t := Tensor{} a := 1 in arr[0..] _ := a _ := 1 in arr[..2] @@ -9,4 +16,6 @@ fn fn_contains_index_expr() { _ := arr[2..] _ := arr[..2] _ := arr[1..3] + _ := t[1..3, ..] + _ := t[1, 2] } diff --git a/vlib/v/fmt/tests/array_slices_input.vv b/vlib/v/fmt/tests/array_slices_input.vv index d70add584..afd4936f6 100644 --- a/vlib/v/fmt/tests/array_slices_input.vv +++ b/vlib/v/fmt/tests/array_slices_input.vv @@ -1,7 +1,9 @@ - +struct Tensor{} +fn (t Tensor) [] (parts []SliceIndex) string {return ''} fn fn_contains_index_expr() { arr := [1, 2, 3, 4, 5] + t:=Tensor{} a := 1 in arr[ 0.. ] _ := a _ := 1 in arr[ ..2 ] @@ -11,4 +13,6 @@ _ := a _ := arr[2 ..] _ := arr[.. 2 ] _ := arr[ 1 .. 3] + _ := t[1..3, ..] + _ := t [ 1,2] } diff --git a/vlib/v/generics/generics.v b/vlib/v/generics/generics.v index 2e66e99be..ecd012fc8 100644 --- a/vlib/v/generics/generics.v +++ b/vlib/v/generics/generics.v @@ -962,6 +962,10 @@ pub fn (mut g Generics) expr(mut node ast.Expr) ast.Expr { node.expr = g.expr(mut node.expr) } ast.IndexExpr { + mut indices := []ast.Expr{cap: node.indices.len} + for mut index in node.indices { + indices << g.expr(mut index) + } if g.cur_concrete_types.len > 0 { return ast.Expr(ast.IndexExpr{ ...node @@ -969,11 +973,13 @@ pub fn (mut g Generics) expr(mut node ast.Expr) ast.Expr { typ: g.unwrap_generic(node.typ) left: g.expr(mut node.left) index: g.expr(mut node.index) + indices: indices or_expr: g.expr(mut node.or_expr) as ast.OrExpr }) } node.left = g.expr(mut node.left) node.index = g.expr(mut node.index) + node.indices = indices node.or_expr = g.expr(mut node.or_expr) as ast.OrExpr } ast.InfixExpr { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 24ebbe069..e2fbf2ddd 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2067,157 +2067,55 @@ fn (mut p Parser) or_block(err_var_mode OrBlockErrVarMode) ([]ast.Stmt, token.Po return stmts, pos, or_scope } -fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { - // left == `a` in `a[0]` - start_pos := p.tok.pos() - p.next() // [ - mut has_low := true +fn (mut p Parser) index_expr_part(is_gated bool) ast.Expr { + part_start_pos := p.tok.pos() if p.tok.kind == .dotdot { - has_low = false - // [..end] p.next() mut high := ast.empty_expr mut has_high := false - if p.tok.kind != .rsbr { + if p.tok.kind !in [.comma, .rsbr] { high = p.expr(0) has_high = true } - - pos_high := start_pos.extend(p.tok.pos()) - p.check(.rsbr) - mut or_kind_high := ast.OrKind.absent - mut or_stmts_high := []ast.Stmt{} - mut or_pos_high := token.Pos{} - mut or_scope := ast.empty_scope - - if !p.or_is_handled { - // a[..end] or {...} - if p.tok.kind == .key_orelse { - or_stmts_high, or_pos_high, or_scope = p.or_block(.no_err_var) - return ast.IndexExpr{ - left: left - pos: pos_high - index: ast.RangeExpr{ - low: ast.empty_expr - high: high - has_high: has_high - pos: pos_high - is_gated: is_gated - } - or_expr: ast.OrExpr{ - kind: .block - stmts: or_stmts_high - pos: or_pos_high - scope: or_scope - } - is_gated: is_gated - } - } - // `a[start..end]!` - if p.tok.kind == .not { - or_pos_high = p.tok.pos() - or_kind_high = .propagate_result - or_scope = p.scope - p.next() - } else if p.tok.kind == .question { - p.error_with_pos('`?` for propagating errors from index expressions is no longer supported, use `!` instead of `?`', - p.tok.pos()) - } - } - - return ast.IndexExpr{ - left: left - pos: pos_high - index: ast.RangeExpr{ - low: ast.empty_expr - high: high - has_high: has_high - pos: pos_high - is_gated: is_gated - } - or_expr: ast.OrExpr{ - kind: or_kind_high - stmts: or_stmts_high - scope: or_scope - pos: or_pos_high - } + return ast.RangeExpr{ + low: ast.empty_expr + high: high + has_high: has_high + pos: part_start_pos.extend(p.prev_tok.pos()) is_gated: is_gated } } - expr := p.expr(0) // `[expr]` or `[expr..` + expr := p.expr(0) + if p.tok.kind != .dotdot { + return expr + } + p.next() + mut high := ast.empty_expr mut has_high := false + if p.tok.kind !in [.comma, .rsbr] { + high = p.expr(0) + has_high = true + } + return ast.RangeExpr{ + low: expr + high: high + has_low: true + has_high: has_high + pos: part_start_pos.extend(p.prev_tok.pos()) + is_gated: is_gated + } +} - if p.tok.kind == .dotdot { - // either [start..end] or [start..] +fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { + // left == `a` in `a[0]` + start_pos := p.tok.pos() + p.next() // [ + mut indices := []ast.Expr{} + indices << p.index_expr_part(is_gated) + for p.tok.kind == .comma && p.tok.pos().line_nr == start_pos.line_nr { p.next() - mut high := ast.empty_expr - if p.tok.kind != .rsbr { - has_high = true - high = p.expr(0) - } - pos_low := start_pos.extend(p.tok.pos()) - p.check(.rsbr) - mut or_kind_low := ast.OrKind.absent - mut or_stmts_low := []ast.Stmt{} - mut or_pos_low := token.Pos{} - mut or_scope := ast.empty_scope - if !p.or_is_handled { - // a[start..end] or {...} - if p.tok.kind == .key_orelse { - or_stmts_low, or_pos_low, or_scope = p.or_block(.no_err_var) - return ast.IndexExpr{ - left: left - pos: pos_low - index: ast.RangeExpr{ - low: expr - high: high - has_high: has_high - has_low: has_low - pos: pos_low - is_gated: is_gated - } - or_expr: ast.OrExpr{ - kind: .block - stmts: or_stmts_low - pos: or_pos_low - scope: or_scope - } - is_gated: is_gated - } - } - // `a[start..end]!` - if p.tok.kind == .not { - or_pos_low = p.tok.pos() - or_kind_low = .propagate_result - or_scope = p.scope - p.next() - } else if p.tok.kind == .question { - p.error_with_pos('`?` for propagating errors from index expressions is no longer supported, use `!` instead of `?`', - p.tok.pos()) - } - } - - return ast.IndexExpr{ - left: left - pos: pos_low - index: ast.RangeExpr{ - low: expr - high: high - has_high: has_high - has_low: has_low - pos: pos_low - is_gated: is_gated - } - or_expr: ast.OrExpr{ - kind: or_kind_low - stmts: or_stmts_low - scope: or_scope - pos: or_pos_low - } - is_gated: is_gated - } + indices << p.index_expr_part(is_gated) } - // [expr] pos := start_pos.extend(p.tok.pos()) p.check(.rsbr) mut or_kind := ast.OrKind.absent @@ -2230,7 +2128,8 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { or_stmts, or_pos, or_scope = p.or_block(.no_err_var) return ast.IndexExpr{ left: left - index: expr + index: indices[0] + indices: indices pos: pos or_expr: ast.OrExpr{ kind: .block @@ -2254,7 +2153,8 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { } return ast.IndexExpr{ left: left - index: expr + index: indices[0] + indices: indices pos: pos or_expr: ast.OrExpr{ kind: or_kind diff --git a/vlib/v/tests/structs/operator_overloading_index_test.v b/vlib/v/tests/structs/operator_overloading_index_test.v index af83c76fe..867ba613a 100644 --- a/vlib/v/tests/structs/operator_overloading_index_test.v +++ b/vlib/v/tests/structs/operator_overloading_index_test.v @@ -13,6 +13,65 @@ fn (mut l IntList) []= (index int, value int) { l.values[index] = value } +struct Tensor { +mut: + last_set int + last_signature int +} + +fn (t Tensor) [] (parts []SliceIndex) int { + mut total := 0 + for part in parts { + if part.is_range { + total += 100 + if part.has_low { + total += part.low * 10 + } + if part.has_high { + total += part.high + } + } else { + total += part.value + } + } + return total +} + +fn (mut t Tensor) []= (parts []SliceIndex, value int) { + mut total := 0 + for part in parts { + if part.is_range { + total += 100 + if part.has_low { + total += part.low * 10 + } + if part.has_high { + total += part.high + } + } else { + total += part.value + } + } + t.last_signature = total + t.last_set = value +} + +struct Axis {} + +fn (a Axis) [] (part SliceIndex) int { + if part.is_range { + mut total := 100 + if part.has_low { + total += part.low * 10 + } + if part.has_high { + total += part.high + } + return total + } + return part.value +} + fn test_operator_overloading_index() { mut list := IntList{ values: [3, 5, 8] @@ -25,3 +84,20 @@ fn test_operator_overloading_index() { list[0] *= 4 assert list[0] == 12 } + +fn test_operator_overloading_multi_index() { + mut tensor := Tensor{} + assert tensor[1, 2] == 3 + assert tensor[1..3, .., 4..] == 353 + tensor[2, ..3] = 7 + assert tensor.last_signature == 105 + assert tensor.last_set == 7 + tensor[1, 3..] += 1 + assert tensor.last_signature == 131 + assert tensor.last_set == 132 + + axis := Axis{} + assert axis[2] == 2 + assert axis[..3] == 103 + assert axis[4..] == 140 +} diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index be8bd518d..85c639ff5 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -614,6 +614,11 @@ pub fn (mut t Transformer) expr(mut node ast.Expr) ast.Expr { t.check_safe_array(mut node) node.left = t.expr(mut node.left) node.index = t.expr(mut node.index) + mut indices := []ast.Expr{cap: node.indices.len} + for mut index in node.indices { + indices << t.expr(mut index) + } + node.indices = indices node.or_expr = t.expr(mut node.or_expr) as ast.OrExpr } ast.InfixExpr { -- 2.39.5