From 03947c8bd69e101474701862d96c206cb020d182 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 16 Feb 2026 02:51:22 +0300 Subject: [PATCH] v2: move array init with index from cleanc to transformer --- cmd/v2/test.v | 32 +++++ vlib/v2/gen/cleanc/cleanc.v | 17 +-- vlib/v2/gen/cleanc/expr.v | 4 - vlib/v2/gen/cleanc/fn.v | 26 ---- vlib/v2/profiler/alloc.v | 2 +- vlib/v2/profiler/context.v | 2 +- vlib/v2/profiler/profiler_alloc.v | 2 +- vlib/v2/profiler/runtime.v | 2 +- vlib/v2/transformer/struct.v | 199 ++++++++++++++++++++++++++++++ vlib/v2/types/checker.v | 3 +- vlib/v2/types/module.v | 1 + 11 files changed, 243 insertions(+), 47 deletions(-) diff --git a/cmd/v2/test.v b/cmd/v2/test.v index ce3e35baa..6455036f4 100644 --- a/cmd/v2/test.v +++ b/cmd/v2/test.v @@ -1659,6 +1659,32 @@ fn test_variadic_call_lowering() { print_str('variadic call lowering (fixed + variadic): ok') } +fn test_array_init_with_index() { + // 99.1 Basic array init with index + arr1 := []int{len: 5, init: index * 2} + assert arr1[0] == 0 + assert arr1[1] == 2 + assert arr1[2] == 4 + assert arr1[3] == 6 + assert arr1[4] == 8 + print_str('array init with index (basic multiply): ok') + + // 99.2 Array init with index addition + arr2 := []int{len: 4, init: index + 10} + assert arr2[0] == 10 + assert arr2[1] == 11 + assert arr2[2] == 12 + assert arr2[3] == 13 + print_str('array init with index (addition): ok') + + // 99.3 Array init with just index + arr3 := []int{len: 3, init: index} + assert arr3[0] == 0 + assert arr3[1] == 1 + assert arr3[2] == 2 + print_str('array init with index (identity): ok') +} + // ===================== MAIN TEST FUNCTION ===================== fn main() { @@ -5230,5 +5256,11 @@ fn main() { print_str('--- Test 98: Variadic call lowering ---') test_variadic_call_lowering() + // ==================== 99. ARRAY INIT WITH INDEX ==================== + // Tests that array initialization with `index` in the init expression + // is correctly expanded by the transformer into a for-loop. + print_str('--- Test 99: Array init with index ---') + test_array_init_with_index() + print_str('=== All tests completed ===') } diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index 38a109e1a..6959e5ac5 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -61,11 +61,11 @@ mut: exported_const_seen map[string]bool exported_const_symbols []ExportedConstSymbol cur_file_name string - in_array_init_index bool // true when generating init expr with `index` - used_fn_keys map[string]bool - called_fn_names map[string]bool - anon_fn_defs []string // lifted anonymous function definitions - pass5_start_pos int // position in sb where pass 5 starts + + used_fn_keys map[string]bool + called_fn_names map[string]bool + anon_fn_defs []string // lifted anonymous function definitions + pass5_start_pos int // position in sb where pass 5 starts } struct ExportedConstSymbol { @@ -832,13 +832,6 @@ fn (mut g Gen) gen_keyword(node ast.Keyword) { } } -// gen_array_init_with_index generates an inline loop for __new_array_with_default_noscan -// when the init expression uses `index`. Expands to: -// ({ array _arr = __new_array_with_default_noscan(len, cap, sizeof(T), NULL); -// for (int _v_index = 0; _v_index < _arr.len; _v_index++) { -// ((T*)_arr.data)[_v_index] = init_expr; -// } _arr; }) - fn strip_expr_wrappers(expr ast.Expr) ast.Expr { return match expr { ast.ParenExpr { diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index e9fd8868c..28f726cfe 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -265,10 +265,6 @@ fn (mut g Gen) expr(node ast.Expr) { ident_name = ident_name[g.cur_module.len + 2..] } } - // Rename `index` to `_v_index` only inside array init loop expansion - if ident_name == 'index' && g.in_array_init_index { - ident_name = '_v_index' - } // Rename V variables that clash with C type names if ident_name == 'array' { ident_name = '_v_array' diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index 81bed582c..07b45531d 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -1625,32 +1625,6 @@ fn (g &Gen) env_fn_scope_by_key(key string) ?&types.Scope { return g.env.get_fn_scope_by_key(key) } -fn (mut g Gen) gen_array_init_with_index(call_args []ast.Expr) { - g.tmp_counter++ - tmp := g.tmp_counter - arr_name := '_awi_t${tmp}' - // Extract sizeof type from 3rd arg (KeywordOperator sizeof) - sizeof_arg := call_args[2] - mut elem_type := 'int' - if sizeof_arg is ast.KeywordOperator && sizeof_arg.op == .key_sizeof { - if sizeof_arg.exprs.len > 0 { - elem_type = g.expr_type_to_c(sizeof_arg.exprs[0]) - } - } - g.sb.write_string('({ array ${arr_name} = __new_array_with_default_noscan(') - g.expr(call_args[0]) - g.sb.write_string(', ') - g.expr(call_args[1]) - g.sb.write_string(', sizeof(${elem_type}), NULL); ') - g.sb.write_string('for (int _v_index = 0; _v_index < ${arr_name}.len; _v_index++) { ') - g.sb.write_string('((${elem_type}*)${arr_name}.data)[_v_index] = ') - // Generate init expression with `index` → `_v_index` rename active - g.in_array_init_index = true - g.expr(call_args[3]) - g.in_array_init_index = false - g.sb.write_string('; } ${arr_name}; })') -} - fn (mut g Gen) gen_fn_literal(node ast.FnLiteral) { anon_name := '_anon_fn_${g.tmp_counter}' g.tmp_counter++ diff --git a/vlib/v2/profiler/alloc.v b/vlib/v2/profiler/alloc.v index 1b014ca4d..0462e3789 100644 --- a/vlib/v2/profiler/alloc.v +++ b/vlib/v2/profiler/alloc.v @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. +// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module profiler diff --git a/vlib/v2/profiler/context.v b/vlib/v2/profiler/context.v index 6f71c8bff..38226569c 100644 --- a/vlib/v2/profiler/context.v +++ b/vlib/v2/profiler/context.v @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. +// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module profiler diff --git a/vlib/v2/profiler/profiler_alloc.v b/vlib/v2/profiler/profiler_alloc.v index 67fae0f9c..b6812bdd2 100644 --- a/vlib/v2/profiler/profiler_alloc.v +++ b/vlib/v2/profiler/profiler_alloc.v @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. +// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module profiler diff --git a/vlib/v2/profiler/runtime.v b/vlib/v2/profiler/runtime.v index 6eb6847e8..2cc805fad 100644 --- a/vlib/v2/profiler/runtime.v +++ b/vlib/v2/profiler/runtime.v @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. +// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module profiler diff --git a/vlib/v2/transformer/struct.v b/vlib/v2/transformer/struct.v index 7d34b1f6f..cf3fe69b5 100644 --- a/vlib/v2/transformer/struct.v +++ b/vlib/v2/transformer/struct.v @@ -179,6 +179,11 @@ fn (mut t Transformer) transform_array_init_expr(expr ast.ArrayInitExpr) ast.Exp name: 'nil' }) }) + // If init expression uses `index`, expand to a for-loop that assigns each element. + if expr.init !is ast.EmptyExpr && t.expr_contains_ident_named(init_expr, 'index') { + return t.expand_array_init_with_index(len_expr, cap_expr, sizeof_expr, init_expr, + expr.pos) + } return ast.CallExpr{ lhs: ast.Ident{ name: '__new_array_with_default_noscan' @@ -1407,4 +1412,198 @@ fn (t &Transformer) get_struct_field_type(expr ast.SelectorExpr) ?types.Type { return none } +// expand_array_init_with_index expands `[]T{len: n, init: expr_using_index}` into: +// mut _awi_tN = __new_array_with_default_noscan(len, cap, sizeof(T), nil) +// for (_v_index = 0; _v_index < _awi_tN.len; _v_index++) { +// ((T*)_awi_tN.data)[_v_index] = init_expr (with `index` renamed to `_v_index`) +// } +// +fn (mut t Transformer) expand_array_init_with_index(len_expr ast.Expr, cap_expr ast.Expr, sizeof_expr ast.Expr, init_expr ast.Expr, pos token.Pos) ast.Expr { + t.temp_counter++ + arr_name := '_awi_t${t.temp_counter}' + arr_ident := ast.Ident{ + name: arr_name + } + idx_ident := ast.Ident{ + name: '_v_index' + } + + // 1. mut _awi_tN = __new_array_with_default_noscan(len, cap, sizeof(T), nil) + init_stmt := ast.Stmt(ast.AssignStmt{ + op: .decl_assign + lhs: [ + ast.Expr(ast.ModifierExpr{ + kind: .key_mut + expr: arr_ident + }), + ] + rhs: [ + ast.Expr(ast.CallExpr{ + lhs: ast.Ident{ + name: '__new_array_with_default_noscan' + } + args: [ + len_expr, + cap_expr, + ast.Expr(ast.KeywordOperator{ + op: .key_sizeof + exprs: [sizeof_expr] + }), + ast.Expr(ast.Ident{ + name: 'nil' + }), + ] + }), + ] + }) + + // 2. Build assignment: ((T*)_awi_tN.data)[_v_index] = init_expr + // with `index` renamed to `_v_index` + renamed_init := t.replace_ident_named(init_expr, 'index', '_v_index') + elem_assign := ast.Stmt(ast.AssignStmt{ + op: .assign + lhs: [ + ast.Expr(ast.IndexExpr{ + lhs: ast.CastExpr{ + typ: ast.PrefixExpr{ + op: .amp + expr: sizeof_expr + } + expr: ast.SelectorExpr{ + lhs: arr_ident + rhs: ast.Ident{ + name: 'data' + } + } + } + expr: idx_ident + }), + ] + rhs: [renamed_init] + }) + + // 3. for (int _v_index = 0; _v_index < _awi_tN.len; _v_index++) { ... } + for_stmt := ast.Stmt(ast.ForStmt{ + init: ast.AssignStmt{ + op: .decl_assign + lhs: [ast.Expr(idx_ident)] + rhs: [ast.Expr(ast.BasicLiteral{ + kind: .number + value: '0' + })] + } + cond: ast.InfixExpr{ + op: .lt + lhs: idx_ident + rhs: ast.SelectorExpr{ + lhs: arr_ident + rhs: ast.Ident{ + name: 'len' + } + } + } + post: ast.AssignStmt{ + op: .assign + lhs: [ast.Expr(idx_ident)] + rhs: [ + ast.Expr(ast.InfixExpr{ + op: .plus + lhs: idx_ident + rhs: ast.BasicLiteral{ + kind: .number + value: '1' + } + }), + ] + } + stmts: [elem_assign] + }) + + // Emit init + for loop as pending statements + t.pending_stmts << init_stmt + t.pending_stmts << for_stmt + + // Return the temp array ident as the expression value + return arr_ident +} + +// replace_ident_named replaces all occurrences of an identifier named `old_name` +// with a new identifier named `new_name` in an expression tree. +fn (t &Transformer) replace_ident_named(expr ast.Expr, old_name string, new_name string) ast.Expr { + match expr { + ast.Ident { + if expr.name == old_name { + return ast.Ident{ + name: new_name + pos: expr.pos + } + } + return expr + } + ast.InfixExpr { + return ast.InfixExpr{ + op: expr.op + lhs: t.replace_ident_named(expr.lhs, old_name, new_name) + rhs: t.replace_ident_named(expr.rhs, old_name, new_name) + pos: expr.pos + } + } + ast.PrefixExpr { + return ast.PrefixExpr{ + op: expr.op + expr: t.replace_ident_named(expr.expr, old_name, new_name) + pos: expr.pos + } + } + ast.ParenExpr { + return ast.ParenExpr{ + expr: t.replace_ident_named(expr.expr, old_name, new_name) + pos: expr.pos + } + } + ast.CallExpr { + mut new_args := []ast.Expr{cap: expr.args.len} + for arg in expr.args { + new_args << t.replace_ident_named(arg, old_name, new_name) + } + return ast.CallExpr{ + lhs: t.replace_ident_named(expr.lhs, old_name, new_name) + args: new_args + pos: expr.pos + } + } + ast.CastExpr { + return ast.CastExpr{ + typ: expr.typ + expr: t.replace_ident_named(expr.expr, old_name, new_name) + pos: expr.pos + } + } + ast.IndexExpr { + return ast.IndexExpr{ + lhs: t.replace_ident_named(expr.lhs, old_name, new_name) + expr: t.replace_ident_named(expr.expr, old_name, new_name) + pos: expr.pos + } + } + ast.SelectorExpr { + return ast.SelectorExpr{ + lhs: t.replace_ident_named(expr.lhs, old_name, new_name) + rhs: expr.rhs + pos: expr.pos + } + } + ast.ModifierExpr { + return ast.ModifierExpr{ + kind: expr.kind + expr: t.replace_ident_named(expr.expr, old_name, new_name) + pos: expr.pos + } + } + else { + return expr + } + } +} + // get_array_type_str returns the Array_T type string for an array expression using checker type info. diff --git a/vlib/v2/types/checker.v b/vlib/v2/types/checker.v index 44efe8162..fc4246e5c 100644 --- a/vlib/v2/types/checker.v +++ b/vlib/v2/types/checker.v @@ -377,7 +377,8 @@ pub fn (mut c Checker) preregister_scopes(file ast.File) { mod_scope := c.get_module_scope(file.mod, builtin_scope) c.scope = mod_scope // add self (own module) for constants. can use own module prefix inside module - c.scope.insert(file.mod, Module{ name: file.mod, scope: c.get_module_scope(file.mod, builtin_scope) }) + c.scope.insert(file.mod, Module{ name: file.mod, scope: c.get_module_scope(file.mod, + builtin_scope) }) // add imports for imp in file.imports { mod := if imp.is_aliased { imp.name.all_after_last('.') } else { imp.alias } diff --git a/vlib/v2/types/module.v b/vlib/v2/types/module.v index c0f10c162..4de7e3b57 100644 --- a/vlib/v2/types/module.v +++ b/vlib/v2/types/module.v @@ -4,6 +4,7 @@ module types pub struct Module { +pub: name string path string imports []Module -- 2.39.5