Skip to content

Commit f7544e3

Browse files
committed
cgen, checker: generics, unsafe { nil } fixes
1 parent fd64037 commit f7544e3

6 files changed

Lines changed: 145 additions & 5 deletions

File tree

vlib/v/checker/checker.v

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,22 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) {
731731
c.post_process_generic_fns() or { break post_process_iterations_loop }
732732
}
733733
}
734+
// Resolve newly created generic struct/interface instances to concrete types.
735+
// This ensures that methods of generic structs instantiated during the current
736+
// iteration (e.g. EluLayer[f64] created when checking elu_layer[f64]) get their
737+
// concrete types registered for rechecking in the next iteration.
738+
mut old_concrete_count := 0
739+
for _, v in c.table.fn_generic_types {
740+
old_concrete_count += v.len
741+
}
742+
c.table.generic_insts_to_concrete()
743+
mut new_concrete_count := 0
744+
for _, v in c.table.fn_generic_types {
745+
new_concrete_count += v.len
746+
}
747+
if new_concrete_count != old_concrete_count {
748+
c.need_recheck_generic_fns = true
749+
}
734750
if !c.need_recheck_generic_fns {
735751
break
736752
}

vlib/v/gen/c/cgen.v

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3815,7 +3815,12 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ
38153815
{
38163816
is_nil_cast := expr is ast.UnsafeExpr && expr.expr is ast.Nil
38173817
if is_nil_cast {
3818-
g.write('((void*)0)')
3818+
if expected_type.is_ptr() {
3819+
g.write('((void*)0)')
3820+
} else {
3821+
// Non-pointer interface is a struct in C, use zero-init
3822+
g.write('(${g.styp(expected_type)}){0}')
3823+
}
38193824
return
38203825
}
38213826
}

vlib/v/gen/c/index.v

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,20 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) {
215215
} else {
216216
sym.info as ast.Array
217217
}
218-
resolved_elem_type := g.recheck_concrete_type(g.resolved_expr_type(node, node.typ))
218+
resolved_elem_type_ := g.recheck_concrete_type(g.resolved_expr_type(node, node.typ))
219+
resolved_elem_type := if resolved_elem_type_ == ast.int_literal_type {
220+
ast.int_type
221+
} else if resolved_elem_type_ == ast.float_literal_type {
222+
ast.f64_type
223+
} else {
224+
resolved_elem_type_
225+
}
219226
elem_type := if resolved_elem_type != 0 && resolved_elem_type != ast.void_type {
220227
resolved_elem_type
228+
} else if info.elem_type == ast.int_literal_type {
229+
ast.int_type
230+
} else if info.elem_type == ast.float_literal_type {
231+
ast.f64_type
221232
} else {
222233
info.elem_type
223234
}

vlib/v/generics/new_generics_regression_test.v

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ fn run_new_generic_solver_tests(root_label string, test_cmd string, expected_sum
114114
println('')
115115
}
116116

117-
const expected_summsvc_generics = 'Summary for all V _test.v files: 103 failed, 171 passed, 274 total.'
117+
const expected_summsvc_generics = 'Summary for all V _test.v files: 104 failed, 171 passed, 275 total.'
118118
// The exact failure count varies slightly across compilers:
119-
// gcc/tcc: 101, clang: 102, msvc/windows-gcc: 103.
120-
const expected_summary_generics = 'Summary for all V _test.v files: 101 failed, 173 passed, 274 total.'
119+
// gcc/tcc: 102, clang: 103, msvc/windows-gcc: 104.
120+
const expected_summary_generics = 'Summary for all V _test.v files: 102 failed, 173 passed, 275 total.'
121121
const expected_summsvc_vec = 'Summary for all V _test.v files: 3 failed, 3 total.'
122122
const expected_summary_vec = 'Summary for all V _test.v files: 3 failed, 3 total.'
123123
const expected_summsvc_flag = 'Summary for all V _test.v files: 2 failed, 17 passed, 19 total.'
@@ -183,6 +183,7 @@ const failing_tests = [
183183
'vlib/v/tests/generics/generics_fn_return_result_test.v',
184184
'vlib/v/tests/generics/generics_fn_variable_3_test.v',
185185
'vlib/v/tests/generics/generics_for_in_iterate_test.v',
186+
'vlib/v/tests/generics/generics_interface_cross_module_recheck_test.v',
186187
'vlib/v/tests/generics/generics_interface_method_test.v',
187188
'vlib/v/tests/generics/generics_interface_with_generic_method_using_generic_struct_test.v',
188189
'vlib/v/tests/generics/generics_interface_with_generic_sumtype_test.v',
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Tests that @[direct_array_access] with compound assignment (+=) works
2+
// correctly in generic functions. Previously, the element type was emitted
3+
// as int_literal (8 bytes) instead of int (4 bytes) in the generated C,
4+
// causing incorrect pointer arithmetic and corrupted array values.
5+
6+
@[heap]
7+
struct Container[T] {
8+
shape []int
9+
}
10+
11+
@[direct_array_access]
12+
fn (c &Container[T]) cumsum[T]() []int {
13+
mut sizes := [0]
14+
sizes << [1, 1, 1]
15+
mut rt := 0
16+
for i in 0 .. sizes.len {
17+
tmp := rt
18+
rt += sizes[i]
19+
sizes[i] += tmp
20+
}
21+
return sizes
22+
}
23+
24+
fn test_generic_direct_array_access_compound_assign() {
25+
c := Container[f64]{
26+
shape: [3, 3]
27+
}
28+
result := c.cumsum()
29+
assert result == [0, 1, 2, 3], 'expected [0, 1, 2, 3] got ${result}'
30+
}
31+
32+
fn test_generic_direct_array_access_compound_assign_int() {
33+
c := Container[int]{
34+
shape: [3]
35+
}
36+
result := c.cumsum()
37+
assert result == [0, 1, 2, 3], 'expected [0, 1, 2, 3] got ${result}'
38+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Tests that methods of generic structs implementing generic interfaces
2+
// are properly type-checked when the struct is instantiated indirectly
3+
// (e.g. through a function that constructs the struct and returns it as
4+
// the interface type). Previously, generic_insts_to_concrete() would
5+
// register concrete types for these methods only after the checker's
6+
// post_process_generic_fns loop had already finished, causing cgen to
7+
// attempt code generation for unchecked AST nodes.
8+
9+
interface Processor[T] {
10+
process(val T) T
11+
}
12+
13+
struct Doubler[T] {
14+
factor T
15+
}
16+
17+
fn (d &Doubler[T]) process(val T) T {
18+
return val * d.factor
19+
}
20+
21+
struct Negator[T] {
22+
offset T
23+
}
24+
25+
fn (n &Negator[T]) process(val T) T {
26+
return -val + n.offset
27+
}
28+
29+
fn make_doubler[T](factor T) Processor[T] {
30+
return Processor[T](&Doubler[T]{
31+
factor: factor
32+
})
33+
}
34+
35+
fn make_negator[T](offset T) Processor[T] {
36+
return Processor[T](&Negator[T]{
37+
offset: offset
38+
})
39+
}
40+
41+
struct Pipeline[T] {
42+
mut:
43+
processors []Processor[T]
44+
}
45+
46+
fn (mut p Pipeline[T]) add_doubler(factor T) {
47+
p.processors << make_doubler[T](factor)
48+
}
49+
50+
fn (mut p Pipeline[T]) add_negator(offset T) {
51+
p.processors << make_negator[T](offset)
52+
}
53+
54+
fn (p &Pipeline[T]) run(val T) T {
55+
mut result := val
56+
for proc in p.processors {
57+
result = proc.process(result)
58+
}
59+
return result
60+
}
61+
62+
fn test_generic_interface_cross_module_recheck() {
63+
mut p := Pipeline[f64]{}
64+
// Only add_doubler is called; add_negator is never called.
65+
// But Negator[f64] still gets instantiated via generic_insts_to_concrete
66+
// and its process method must be properly type-checked.
67+
p.add_doubler(3.0)
68+
assert p.run(5.0) == 15.0
69+
}

0 commit comments

Comments
 (0)