From f7544e3484f4afb29d56a2e3cfcab9502092559d Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 7 Apr 2026 21:07:17 +0300 Subject: [PATCH] cgen, checker: generics, unsafe { nil } fixes --- vlib/v/checker/checker.v | 16 +++++ vlib/v/gen/c/cgen.v | 7 +- vlib/v/gen/c/index.v | 13 +++- .../v/generics/new_generics_regression_test.v | 7 +- ...direct_array_access_compound_assign_test.v | 38 ++++++++++ ...rics_interface_cross_module_recheck_test.v | 69 +++++++++++++++++++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 vlib/v/tests/builtin_arrays/generic_direct_array_access_compound_assign_test.v create mode 100644 vlib/v/tests/generics/generics_interface_cross_module_recheck_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 425080727..6b2f9ea8e 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -731,6 +731,22 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { c.post_process_generic_fns() or { break post_process_iterations_loop } } } + // Resolve newly created generic struct/interface instances to concrete types. + // This ensures that methods of generic structs instantiated during the current + // iteration (e.g. EluLayer[f64] created when checking elu_layer[f64]) get their + // concrete types registered for rechecking in the next iteration. + mut old_concrete_count := 0 + for _, v in c.table.fn_generic_types { + old_concrete_count += v.len + } + c.table.generic_insts_to_concrete() + mut new_concrete_count := 0 + for _, v in c.table.fn_generic_types { + new_concrete_count += v.len + } + if new_concrete_count != old_concrete_count { + c.need_recheck_generic_fns = true + } if !c.need_recheck_generic_fns { break } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 31f783ebf..d543814cb 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3815,7 +3815,12 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ { is_nil_cast := expr is ast.UnsafeExpr && expr.expr is ast.Nil if is_nil_cast { - g.write('((void*)0)') + if expected_type.is_ptr() { + g.write('((void*)0)') + } else { + // Non-pointer interface is a struct in C, use zero-init + g.write('(${g.styp(expected_type)}){0}') + } return } } diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index ff2d688b3..d0cdbcb4e 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -215,9 +215,20 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { } else { sym.info as ast.Array } - resolved_elem_type := g.recheck_concrete_type(g.resolved_expr_type(node, node.typ)) + resolved_elem_type_ := g.recheck_concrete_type(g.resolved_expr_type(node, node.typ)) + resolved_elem_type := if resolved_elem_type_ == ast.int_literal_type { + ast.int_type + } else if resolved_elem_type_ == ast.float_literal_type { + ast.f64_type + } else { + resolved_elem_type_ + } elem_type := if resolved_elem_type != 0 && resolved_elem_type != ast.void_type { resolved_elem_type + } else if info.elem_type == ast.int_literal_type { + ast.int_type + } else if info.elem_type == ast.float_literal_type { + ast.f64_type } else { info.elem_type } diff --git a/vlib/v/generics/new_generics_regression_test.v b/vlib/v/generics/new_generics_regression_test.v index bfc26bfba..4d178bcfe 100644 --- a/vlib/v/generics/new_generics_regression_test.v +++ b/vlib/v/generics/new_generics_regression_test.v @@ -114,10 +114,10 @@ fn run_new_generic_solver_tests(root_label string, test_cmd string, expected_sum println('') } -const expected_summsvc_generics = 'Summary for all V _test.v files: 103 failed, 171 passed, 274 total.' +const expected_summsvc_generics = 'Summary for all V _test.v files: 104 failed, 171 passed, 275 total.' // The exact failure count varies slightly across compilers: -// gcc/tcc: 101, clang: 102, msvc/windows-gcc: 103. -const expected_summary_generics = 'Summary for all V _test.v files: 101 failed, 173 passed, 274 total.' +// gcc/tcc: 102, clang: 103, msvc/windows-gcc: 104. +const expected_summary_generics = 'Summary for all V _test.v files: 102 failed, 173 passed, 275 total.' const expected_summsvc_vec = 'Summary for all V _test.v files: 3 failed, 3 total.' const expected_summary_vec = 'Summary for all V _test.v files: 3 failed, 3 total.' const expected_summsvc_flag = 'Summary for all V _test.v files: 2 failed, 17 passed, 19 total.' @@ -183,6 +183,7 @@ const failing_tests = [ 'vlib/v/tests/generics/generics_fn_return_result_test.v', 'vlib/v/tests/generics/generics_fn_variable_3_test.v', 'vlib/v/tests/generics/generics_for_in_iterate_test.v', + 'vlib/v/tests/generics/generics_interface_cross_module_recheck_test.v', 'vlib/v/tests/generics/generics_interface_method_test.v', 'vlib/v/tests/generics/generics_interface_with_generic_method_using_generic_struct_test.v', 'vlib/v/tests/generics/generics_interface_with_generic_sumtype_test.v', diff --git a/vlib/v/tests/builtin_arrays/generic_direct_array_access_compound_assign_test.v b/vlib/v/tests/builtin_arrays/generic_direct_array_access_compound_assign_test.v new file mode 100644 index 000000000..33fa5c98d --- /dev/null +++ b/vlib/v/tests/builtin_arrays/generic_direct_array_access_compound_assign_test.v @@ -0,0 +1,38 @@ +// Tests that @[direct_array_access] with compound assignment (+=) works +// correctly in generic functions. Previously, the element type was emitted +// as int_literal (8 bytes) instead of int (4 bytes) in the generated C, +// causing incorrect pointer arithmetic and corrupted array values. + +@[heap] +struct Container[T] { + shape []int +} + +@[direct_array_access] +fn (c &Container[T]) cumsum[T]() []int { + mut sizes := [0] + sizes << [1, 1, 1] + mut rt := 0 + for i in 0 .. sizes.len { + tmp := rt + rt += sizes[i] + sizes[i] += tmp + } + return sizes +} + +fn test_generic_direct_array_access_compound_assign() { + c := Container[f64]{ + shape: [3, 3] + } + result := c.cumsum() + assert result == [0, 1, 2, 3], 'expected [0, 1, 2, 3] got ${result}' +} + +fn test_generic_direct_array_access_compound_assign_int() { + c := Container[int]{ + shape: [3] + } + result := c.cumsum() + assert result == [0, 1, 2, 3], 'expected [0, 1, 2, 3] got ${result}' +} diff --git a/vlib/v/tests/generics/generics_interface_cross_module_recheck_test.v b/vlib/v/tests/generics/generics_interface_cross_module_recheck_test.v new file mode 100644 index 000000000..6daceefc6 --- /dev/null +++ b/vlib/v/tests/generics/generics_interface_cross_module_recheck_test.v @@ -0,0 +1,69 @@ +// Tests that methods of generic structs implementing generic interfaces +// are properly type-checked when the struct is instantiated indirectly +// (e.g. through a function that constructs the struct and returns it as +// the interface type). Previously, generic_insts_to_concrete() would +// register concrete types for these methods only after the checker's +// post_process_generic_fns loop had already finished, causing cgen to +// attempt code generation for unchecked AST nodes. + +interface Processor[T] { + process(val T) T +} + +struct Doubler[T] { + factor T +} + +fn (d &Doubler[T]) process(val T) T { + return val * d.factor +} + +struct Negator[T] { + offset T +} + +fn (n &Negator[T]) process(val T) T { + return -val + n.offset +} + +fn make_doubler[T](factor T) Processor[T] { + return Processor[T](&Doubler[T]{ + factor: factor + }) +} + +fn make_negator[T](offset T) Processor[T] { + return Processor[T](&Negator[T]{ + offset: offset + }) +} + +struct Pipeline[T] { +mut: + processors []Processor[T] +} + +fn (mut p Pipeline[T]) add_doubler(factor T) { + p.processors << make_doubler[T](factor) +} + +fn (mut p Pipeline[T]) add_negator(offset T) { + p.processors << make_negator[T](offset) +} + +fn (p &Pipeline[T]) run(val T) T { + mut result := val + for proc in p.processors { + result = proc.process(result) + } + return result +} + +fn test_generic_interface_cross_module_recheck() { + mut p := Pipeline[f64]{} + // Only add_doubler is called; add_negator is never called. + // But Negator[f64] still gets instantiated via generic_insts_to_concrete + // and its process method must be properly type-checked. + p.add_doubler(3.0) + assert p.run(5.0) == 15.0 +} -- 2.39.5