Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,485 @@
// vtest build: macos
module cleanc
import os
import v2.parser
import v2.pref as vpref
import v2.token
import v2.transformer
import v2.types
fn generate_array_append_c_for_test(code string) string {
return generate_array_append_c_for_test_files([code])
}
fn generate_array_append_c_for_test_files(sources []string) string {
tmp_dir := os.join_path(os.temp_dir(), 'v2_array_append_codegen_test_${os.getpid()}')
os.rmdir_all(tmp_dir) or {}
os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
defer {
os.rmdir_all(tmp_dir) or {}
}
mut paths := []string{cap: sources.len}
for i, code in sources {
tmp_file := os.join_path(tmp_dir, 'file_${i}.v')
os.write_file(tmp_file, code) or { panic('failed to write temp file') }
paths << tmp_file
}
prefs := &vpref.Preferences{
backend: .cleanc
no_parallel: true
}
mut file_set := token.FileSet.new()
mut par := parser.Parser.new(prefs)
files := par.parse_files(paths, mut file_set)
env := types.Environment.new()
mut checker := types.Checker.new(prefs, file_set, env)
checker.check_files(files)
mut trans := transformer.Transformer.new_with_pref(env, prefs)
mut gen := Gen.new_with_env_and_pref(trans.transform_files(files), env, prefs)
return gen.gen()
}
fn test_generate_c_uses_push_many_for_array_slice_append() {
csrc := generate_array_append_c_for_test('
fn main() {
mut dst := []u8{}
haystack := [u8(1), 2, 3]
dst << haystack[1..3]
}
')
assert csrc.contains('array__push_many(')
assert !csrc.contains('&(u8[1]){array__slice(haystack, 1, 3)}')
}
fn test_generate_c_uses_push_many_for_array_literal_append() {
csrc := generate_array_append_c_for_test('
fn main() {
mut dst := []u8{}
dst << [u8(`$`)]
}
')
assert csrc.contains('array__push_many(')
assert !csrc.contains('&(u8[1]){new_array_from_c_array(')
}
fn test_generate_c_uses_push_many_for_local_array_append() {
csrc := generate_array_append_c_for_test('
fn source() []u8 {
return [u8(1), 2]
}
fn main() {
mut dst := []u8{}
src := source()
dst << src
}
')
assert csrc.contains('array__push_many(')
assert !csrc.contains('&(u8[1]){src}')
}
fn test_generate_c_push_many_to_mut_array_param_uses_param_pointer() {
csrc := generate_array_append_c_for_test('
fn extend(mut dst []usize, src []usize) {
dst << src
}
')
assert csrc.contains('array__push_many(')
assert csrc.contains('array__push_many(((array*)(dst)),')
assert !csrc.contains('array__push_many((array*)&dst,')
assert !csrc.contains('array__push_many(((array*)(&dst)),')
}
fn test_generate_c_lowered_push_many_to_mut_array_param_uses_param_pointer() {
csrc := generate_array_append_c_for_test('
struct Index {
entries map[string][]usize
}
fn (idx Index) matches_into(candidate string, mut matches []usize) {
if hits := idx.entries[candidate] {
matches << hits
}
}
')
assert csrc.contains('array__push_many(')
assert csrc.contains('array__push_many(((array*)(matches)),')
assert !csrc.contains('array__push_many((array*)&matches,')
assert !csrc.contains('array__push_many(((array*)(&matches)),')
}
fn test_generate_c_uses_push_many_for_fn_pointer_array_return_append() {
csrc := generate_array_append_c_for_test('
fn source(data []u8) []u8 {
return data
}
fn consume(hash_func fn ([]u8) []u8) {
mut dst := []u8{}
src := hash_func(dst)
dst << src
}
fn main() {
consume(source)
}
')
assert csrc.contains('array__push_many(')
assert !csrc.contains('&(u8[1]){src}')
}
fn test_generate_c_keeps_bitwise_or_inside_array_append_value() {
csrc := generate_array_append_c_for_test('
fn main() {
mut bytes := []u8{}
n1 := u8(1)
n0 := u8(2)
bytes << (n1 << 4) | n0
}
')
assert csrc.contains('array__push(')
assert csrc.contains('| n0')
assert !csrc.contains('array__push_many(')
assert !csrc.contains('_arr_push_many_tmp_')
assert !csrc.contains('}) | n0')
}
fn test_generate_c_wraps_mut_u8_array_param_bitwise_append_value() {
csrc := generate_array_append_c_for_test('
fn write_int(mut out []u8, value u64, high_bits u8) {
out << high_bits | u8(value)
}
')
assert csrc.contains('array__push(((array*)(out)), &(u8[1]){')
assert !csrc.contains('array__push(((array*)(out)), (high_bits | ((u8)(value))))')
}
fn test_generate_c_appends_for_mut_pointer_value_to_array() {
csrc := generate_array_append_c_for_test('
struct PullRequest {
mut:
repo_name string
}
fn copy(mut prs []PullRequest) []PullRequest {
mut out := []PullRequest{}
for mut pr in prs {
pr.repo_name = "x"
out << pr
}
return out
}
')
assert csrc.contains('array__push(((array*)(&out)), pr);')
assert !csrc.contains('array__push(((array*)(&out)), &(PullRequest[1]){pr});')
}
fn test_generate_c_indexes_local_array_that_shadows_function_name() {
csrc := generate_array_append_c_for_test('
fn bytes() []u8 {
return []u8{}
}
fn main() {
mut bytes := [u8(1)]
bytes[0] &= u8(3)
}
')
assert csrc.contains('((u8*)bytes.data)[((int)(0))] &= ((u8)(3));')
assert !csrc.contains('bytes &= ((u8)(3));')
}
fn test_generate_c_uses_typed_zero_for_array_tuple_if_declarations() {
csrc := generate_array_append_c_for_test('
fn pick(operand_a []u64, operand_b []u64) {
mut a, mut b := if operand_a.len >= operand_b.len {
operand_a, operand_b
} else {
operand_b, operand_a
}
_ = a
_ = b
}
')
assert csrc.contains('Array_u64 a = ((Array_u64){0});')
assert csrc.contains('Array_u64 b = ((Array_u64){0});')
assert !csrc.contains('Array_u64 a = 0;')
assert !csrc.contains('Array_u64 b = 0;')
}
fn test_generate_c_uses_default_init_for_empty_array_struct_fields() {
csrc := generate_array_append_c_for_test('
struct Pool {
items []voidptr
}
fn main() {
pool := Pool{
items: []
}
_ = pool.items.len
}
')
assert csrc.contains('.items = __new_array_with_default_noscan(0, 0, sizeof(void*)')
assert !csrc.contains('.items = new_array_from_c_array(0, 0, sizeof(void*)')
}
fn test_generate_c_specializes_generic_pointer_array_push_literal() {
csrc := generate_array_append_c_for_test('
struct Foo {}
fn get_results_ref[T](items []voidptr) []&T {
mut res := []&T{cap: items.len}
for i in 0 .. items.len {
res << unsafe { &T(items[i]) }
}
return res
}
fn main() {
items := []voidptr{}
res := get_results_ref[Foo](items)
_ = res.len
}
')
assert csrc.contains('(Foo*[1]){((Foo*)')
assert !csrc.contains('(T*[1])')
assert !csrc.contains('stdatomic__T')
}
fn test_generate_c_uses_captured_mut_array_pointer_for_closure_push() {
csrc := generate_array_append_c_for_test('
fn consume(cb fn (int) bool) {
_ = cb(1)
}
fn main() {
mut values := []int{}
consume(fn [mut values] (value int) bool {
values << value
return true
})
}
')
assert csrc.contains('Array_int* values = _anon_fn_')
assert csrc.contains('array__push(((array*)(values)),')
assert !csrc.contains('array__push(((array*)(&values)),')
}
fn test_generate_c_updates_captured_mut_scalar_for_closure_postfix() {
csrc := generate_array_append_c_for_test('
fn consume(cb fn () bool) {
_ = cb()
}
fn main() {
mut count := u64(0)
consume(fn [mut count] () bool {
count++
return true
})
}
')
assert csrc.contains('u64* count = _anon_fn_')
assert csrc.contains('(*count)++')
assert !csrc.contains('count++;')
}
fn test_generate_c_evaluates_generic_comptime_is_against_implements() {
csrc := generate_array_append_c_for_test('
interface Writer {
write([]u8) !int
}
struct Buffer implements Writer {}
fn (mut b Buffer) write(buf []u8) !int {
_ = b
return buf.len
}
fn choose[T]() int {
$if T is Writer {
return 1
} $else {
return 2
}
}
fn main() {
_ = choose[Buffer]()
}
')
assert csrc.contains('return 1;')
assert !csrc.contains('return 2;')
}
fn test_generate_c_specializes_comptime_pointer_field_generic_call_by_field_type() {
csrc := generate_array_append_c_for_test('
fn struct_field_should_encode[T](val T) bool {
_ = val
return true
}
fn encode_fields[T](val T) bool {
mut ok := true
$for field in T.fields {
ok = struct_field_should_encode(val.$(field.name))
}
return ok
}
struct Node {
next &Node = unsafe { nil }
}
fn main() {
_ = encode_fields(Node{})
}
')
assert csrc.contains('struct_field_should_encode_T_Nodeptr(val.next)')
assert !csrc.contains('struct_field_should_encode_T_Node((*val.next))')
}
fn test_generate_c_specializes_comptime_generic_pointer_field_by_field_type() {
csrc := generate_array_append_c_for_test('
module json2
struct ValueInfo {}
struct Node[T] {
value T
next &Node[T] = unsafe { nil }
}
struct Decoder {
current_node &Node[ValueInfo] = unsafe { nil }
}
struct EncoderFieldInfo {}
struct Encoder {}
fn (mut encoder Encoder) encode_value[T](val T) {
$if T is $pointer {
encoder.encode_value(*val)
} $else $if T is $struct {
_ = encoder.encode_fields[T](val)
}
}
fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo {
return []EncoderFieldInfo{}
}
fn (mut encoder Encoder) encode_struct_field_value[T](val T) {
$if T.indirections == 1 {
encoder.encode_value(*val)
} $else {
encoder.encode_value(val)
}
}
fn struct_field_should_encode[T](field_info EncoderFieldInfo, val T) bool {
_ = field_info
_ = val
return true
}
fn (mut encoder Encoder) encode_fields[T](val T) bool {
field_infos := encoder.cached_field_infos[T]()
mut ok := true
mut i := 0
$for field in T.fields {
field_info := field_infos[i]
ok = struct_field_should_encode(field_info, val.$(field.name))
encoder.encode_struct_field_value(val.$(field.name))
i++
}
return ok
}
fn use_encoder() {
mut encoder := Encoder{}
_ = encoder.encode_fields(Decoder{})
_ = encoder.encode_fields(Node[ValueInfo]{})
}
')
assert csrc.contains('json2__struct_field_should_encode_T_json2_Nodeptr(field_info, val.current_node)')
assert csrc.contains('json2__Encoder__encode_struct_field_value_T_json2_Nodeptr(encoder, val.current_node)')
assert csrc.contains('json2__Encoder__cached_field_infos_T_json2_Decoder(encoder)')
assert csrc.contains('Array_json2__EncoderFieldInfo json2__Encoder__cached_field_infos_T_json2_Node(json2__Encoder* encoder)')
assert !csrc.contains('json2__struct_field_should_encode_T_json2_Node(field_info, (*val.current_node))')
assert !csrc.contains('json2__Encoder__encode_struct_field_value_T_json2_Node(encoder, (*val.current_node))')
}
fn test_generate_c_extracts_array_sum_variant_for_generic_array_param() {
csrc := generate_array_append_c_for_test_files([
'
module orm
pub struct InfixType {
right int
}
pub type Primitive = InfixType | []InfixType | int
pub fn tenant_filter_array_primitive_type[T](value []T) int {
return value.len
}
pub fn tenant_filter_primitive_type(value Primitive) int {
return match value {
InfixType {
2
}
[]InfixType {
tenant_filter_array_primitive_type(value)
}
int {
3
}
}
}
',
'
module main
import orm
fn main() {
_ = orm.tenant_filter_primitive_type([]orm.InfixType{})
}
',
])
assert csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(((((Array_orm__InfixType*)(value._data._Array_orm__InfixType))')
assert !csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(((((orm__InfixType*)(value._data._InfixType))')
}
fn test_generate_c_lowers_global_interface_generic_method_call() {
csrc := generate_array_append_c_for_test('
interface Rng {
intn(max int) !int
}
__global default_rng &Rng
fn (mut rng Rng) element[T](array []T) !T {
if array.len == 0 {
return error("empty")
}
return array[rng.intn(array.len)!]
}
fn element[T](array []T) !T {
return default_rng.element[T](array)
}
fn main() {
_ = element[u8]([u8(1)]) or { u8(0) }
}
')
assert csrc.contains('Rng__element_T_u8(default_rng, _v_array)')
assert !csrc.contains('u8 _val = (_v_array)')
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,324 @@
// 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 cleanc
import strings
import v2.ast
fn (mut g Gen) gen_for_stmt(node ast.ForStmt) {
if node.init is ast.ForInStmt {
if g.gen_map_for_in_stmt(node, node.init) {
return
}
if g.gen_array_for_in_stmt(node, node.init) {
return
}
}
if g.gen_transformed_untyped_map_for_in_stmt(node) {
return
}
g.write_indent()
has_init := !is_empty_stmt(node.init)
has_cond := !is_empty_expr(node.cond)
has_post := !is_empty_stmt(node.post)
if has_init || has_post {
// C-style for loop: for (init; cond; post)
g.sb.write_string('for (')
if has_init {
g.gen_stmt_inline(node.init)
}
g.sb.write_string('; ')
if has_cond {
g.expr(node.cond)
}
g.sb.write_string('; ')
if has_post {
g.gen_stmt_inline(node.post)
}
g.sb.writeln(') {')
} else if has_cond {
// while-style: for cond {
g.sb.write_string('while (')
g.expr(node.cond)
g.sb.writeln(') {')
} else {
// Infinite loop: for {
g.sb.writeln('for (;;) {')
}
g.indent++
// Save runtime_local_types: variables declared in the loop body
// (e.g. _filter_it in nested map/filter) can shadow outer variables.
// Without save/restore, the inner type overwrites the outer in the flat map.
saved_local_types := g.runtime_local_types.clone()
saved_decl_types := g.runtime_decl_types.clone()
g.gen_stmts(node.stmts)
g.runtime_local_types = saved_local_types.clone()
g.runtime_decl_types = saved_decl_types.clone()
g.not_local_var_cache.clear()
g.indent--
g.write_indent()
g.sb.writeln('}')
}
fn (mut g Gen) gen_map_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool {
mut map_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*')
if (map_type == '' || map_type == 'int') && for_in.expr is ast.Ident {
map_type = (g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*')
}
if !map_type.starts_with('Map_') {
return false
}
key_type, value_type := g.parse_map_kv_types(map_type['Map_'.len..])
if key_type == '' || value_type == '' {
return false
}
id := g.tmp_counter
g.tmp_counter++
map_tmp := '_map_iter_${id}'
len_tmp := '_map_len_${id}'
idx_tmp := '_map_idx_${id}'
delta_tmp := '_map_delta_${id}'
mut expr_sb := strings.new_builder(64)
saved_sb := g.sb
g.sb = expr_sb
g.expr(for_in.expr)
map_expr := g.sb.str()
g.sb = saved_sb
g.write_indent()
g.sb.writeln('${map_type} ${map_tmp} = ${map_expr};')
g.write_indent()
g.sb.writeln('int ${len_tmp} = ${map_tmp}.key_values.len;')
g.write_indent()
g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${len_tmp}); ${idx_tmp} = (${idx_tmp} + 1)) {')
g.indent++
g.write_indent()
g.sb.writeln('int ${delta_tmp} = (${map_tmp}.key_values.len - ${len_tmp});')
g.write_indent()
g.sb.writeln('${len_tmp} = ${map_tmp}.key_values.len;')
g.write_indent()
g.sb.writeln('if ((${delta_tmp} < 0)) {')
g.indent++
g.write_indent()
g.sb.writeln('${idx_tmp} = -1;')
g.write_indent()
g.sb.writeln('continue;')
g.indent--
g.write_indent()
g.sb.writeln('}')
g.write_indent()
g.sb.writeln('if (!DenseArray__has_index(&${map_tmp}.key_values, ${idx_tmp})) {')
g.indent++
g.write_indent()
g.sb.writeln('continue;')
g.indent--
g.write_indent()
g.sb.writeln('}')
key_name := if for_in.key is ast.Ident { for_in.key.name } else { '' }
value_name := if for_in.value is ast.Ident { for_in.value.name } else { '' }
saved_local_types := g.runtime_local_types.clone()
saved_decl_types := g.runtime_decl_types.clone()
if key_name != '' && key_name != '_' {
g.write_indent()
g.sb.writeln('${key_type} ${key_name} = *((${key_type}*)DenseArray__key(&${map_tmp}.key_values, ${idx_tmp}));')
if key_type == 'string' {
g.write_indent()
g.sb.writeln('${key_name} = string__clone(${key_name});')
}
g.remember_runtime_local_type(key_name, key_type)
}
if value_name != '' && value_name != '_' {
g.write_indent()
g.sb.writeln('${value_type} ${value_name} = *((${value_type}*)DenseArray__value(&${map_tmp}.key_values, ${idx_tmp}));')
if value_type == 'string' {
g.write_indent()
g.sb.writeln('${value_name} = string__clone(${value_name});')
}
g.remember_runtime_local_type(value_name, value_type)
}
g.gen_stmts(node.stmts)
g.runtime_local_types = saved_local_types.clone()
g.runtime_decl_types = saved_decl_types.clone()
g.not_local_var_cache.clear()
g.indent--
g.write_indent()
g.sb.writeln('}')
return true
}
fn cleanc_for_in_ident_name(expr ast.Expr) (string, bool) {
if expr is ast.Ident {
return expr.name, false
}
if expr is ast.ModifierExpr {
if expr.expr is ast.Ident {
return expr.expr.name, expr.kind == .key_mut
}
}
return '', false
}
fn (mut g Gen) gen_array_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool {
mut array_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*')
if (array_type == '' || array_type == 'int') && for_in.expr is ast.Ident {
array_type =
(g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*')
}
if array_type == '' {
return false
}
mut array_decl_type := array_type
if !c_type_is_array_value(array_decl_type) {
alias_base := g.array_alias_base_type(array_decl_type)
if alias_base != '' {
array_decl_type = alias_base
}
}
if !c_type_is_array_value(array_decl_type) {
return false
}
elem_type := g.array_alias_elem_type_from_c_type(array_decl_type)
if elem_type == '' {
return false
}
id := g.tmp_counter
g.tmp_counter++
array_tmp := '_arr_iter_${id}'
idx_tmp := '_arr_idx_${id}'
mut expr_sb := strings.new_builder(64)
saved_sb := g.sb
g.sb = expr_sb
g.expr(for_in.expr)
array_expr := g.sb.str()
g.sb = saved_sb
g.write_indent()
g.sb.writeln('${array_decl_type} ${array_tmp} = ${array_expr};')
g.write_indent()
g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${array_tmp}.len); ${idx_tmp} = (${idx_tmp} + 1)) {')
g.indent++
key_name, _ := cleanc_for_in_ident_name(for_in.key)
value_name, value_is_mut := cleanc_for_in_ident_name(for_in.value)
saved_local_types := g.runtime_local_types.clone()
saved_decl_types := g.runtime_decl_types.clone()
if key_name != '' && key_name != '_' {
g.write_indent()
g.sb.writeln('int ${key_name} = ${idx_tmp};')
g.remember_runtime_local_type(key_name, 'int')
}
if value_name != '' && value_name != '_' {
g.write_indent()
if value_is_mut {
g.sb.writeln('${elem_type}* ${value_name} = &(((${elem_type}*)${array_tmp}.data)[${idx_tmp}]);')
g.remember_runtime_local_type(value_name, '${elem_type}*')
} else {
g.sb.writeln('${elem_type} ${value_name} = ((${elem_type}*)${array_tmp}.data)[${idx_tmp}];')
if elem_type == 'string' {
g.write_indent()
g.sb.writeln('${value_name} = string__clone(${value_name});')
}
g.remember_runtime_local_type(value_name, elem_type)
}
}
g.gen_stmts(node.stmts)
g.runtime_local_types = saved_local_types.clone()
g.runtime_decl_types = saved_decl_types.clone()
g.not_local_var_cache.clear()
g.indent--
g.write_indent()
g.sb.writeln('}')
return true
}
fn (mut g Gen) gen_transformed_untyped_map_for_in_stmt(node ast.ForStmt) bool {
if node.init !is ast.AssignStmt || node.stmts.len == 0 {
return false
}
init := node.init as ast.AssignStmt
if init.lhs.len == 0 || init.lhs[0] !is ast.Ident {
return false
}
key_name := (init.lhs[0] as ast.Ident).name
first_stmt := node.stmts[0]
if first_stmt !is ast.AssignStmt {
return false
}
value_assign := first_stmt as ast.AssignStmt
if value_assign.lhs.len == 0 || value_assign.rhs.len == 0 || value_assign.lhs[0] !is ast.Ident
|| value_assign.rhs[0] !is ast.IndexExpr {
return false
}
value_name := (value_assign.lhs[0] as ast.Ident).name
index_expr := value_assign.rhs[0] as ast.IndexExpr
mut map_type := g.get_expr_type(index_expr.lhs).trim_space().trim_right('*')
if (map_type == '' || map_type == 'int') && index_expr.lhs is ast.Ident {
map_type =
(g.get_local_var_c_type(index_expr.lhs.name) or { '' }).trim_space().trim_right('*')
}
if !map_type.starts_with('Map_') {
return false
}
map_node := ast.ForStmt{
stmts: node.stmts[1..]
}
return g.gen_map_for_in_stmt(map_node, ast.ForInStmt{
key: ast.Expr(ast.Ident{
name: key_name
})
value: ast.Expr(ast.Ident{
name: value_name
})
expr: index_expr.lhs
})
}
fn (mut g Gen) gen_stmt_inline(node ast.Stmt) {
if !stmt_has_valid_data(node) {
return
}
match node {
ast.AssignStmt {
lhs := node.lhs[0]
rhs := node.rhs[0]
if node.op == .decl_assign {
mut name := ''
if lhs is ast.Ident {
name = lhs.name
}
typ := g.get_expr_type(rhs)
g.sb.write_string('${typ} ${name} = ')
g.expr(rhs)
// Register the for-loop init variable so assignments inside
// the loop body don't re-declare it.
if name != '' && typ != '' {
g.remember_runtime_local_type(name, typ)
}
} else {
g.expr(lhs)
op_str := match node.op {
.assign { '=' }
.plus_assign { '+=' }
.minus_assign { '-=' }
.mul_assign { '*=' }
.div_assign { '/=' }
.mod_assign { '%=' }
.and_assign { '&=' }
.or_assign { '|=' }
.xor_assign { '^=' }
.left_shift_assign { '<<=' }
.right_shift_assign { '>>=' }
else { '=' }
}
g.sb.write_string(' ${op_str} ')
g.expr(rhs)
}
}
ast.ExprStmt {
g.expr(node.expr)
}
else {}
}
}
@@ -0,0 +1,9 @@
module cleanc
fn test_generic_struct_scan_job_count_caps_runtime_jobs() {
assert generic_struct_scan_job_count(128, 1000) == max_generic_struct_scan_jobs
assert generic_struct_scan_job_count(64, 15) == 3
assert generic_struct_scan_job_count(2, 1000) == 2
assert generic_struct_scan_job_count(4, 0) == 0
assert generic_struct_scan_job_count(0, 4) == 0
}
+613
View File
@@ -0,0 +1,613 @@
// 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 cleanc
import v2.ast
fn const_bool_value(expr ast.Expr) ?bool {
match expr {
ast.BasicLiteral {
if expr.kind == .key_true {
return true
}
if expr.kind == .key_false {
return false
}
if expr.value == 'true' {
return true
}
if expr.value == 'false' {
return false
}
}
ast.Ident {
if expr.name == 'true' {
return true
}
if expr.name == 'false' {
return false
}
}
ast.Keyword {
if expr.tok == .key_true {
return true
}
if expr.tok == .key_false {
return false
}
}
ast.ParenExpr {
return const_bool_value(expr.expr)
}
ast.ModifierExpr {
return const_bool_value(expr.expr)
}
ast.CastExpr {
return const_bool_value(expr.expr)
}
ast.ComptimeExpr {
return const_bool_value(expr.expr)
}
ast.UnsafeExpr {
if expr.stmts.len == 1 && expr.stmts[0] is ast.ExprStmt {
stmt := expr.stmts[0] as ast.ExprStmt
return const_bool_value(stmt.expr)
}
}
ast.PrefixExpr {
if expr.op == .not {
if v := const_bool_value(expr.expr) {
return !v
}
}
}
else {}
}
return none
}
fn (mut g Gen) gen_if_expr_assign_value(name string, value_type string, expr ast.Expr) {
g.write_indent()
if value_type.starts_with('Array_fixed_') && !value_type.ends_with('*') {
if expr is ast.CallExpr {
if call_ret := g.get_call_return_type(expr.lhs, expr.args) {
if call_ret == value_type {
wrapper_type := g.c_fn_return_type_from_v(value_type)
g.sb.write_string('{ ${wrapper_type} _tmp = ')
g.expr(expr)
g.sb.writeln('; memcpy(${name}, _tmp.ret_arr, sizeof(${value_type})); }')
return
}
}
}
g.sb.write_string('memcpy(${name}, ')
if expr is ast.ArrayInitExpr {
g.sb.write_string('((${value_type})')
g.expr(expr)
g.sb.write_string(')')
} else {
g.expr(expr)
}
g.sb.writeln(', sizeof(${value_type}));')
return
}
g.sb.write_string('${name} = ')
if g.get_sum_type_variants_for(value_type).len > 0 {
g.gen_type_cast_expr(value_type, expr)
} else {
g.expr(expr)
}
g.sb.writeln(';')
}
fn (mut g Gen) gen_decl_if_expr_branch(name string, value_type string, stmts []ast.Stmt) {
last_idx := last_non_empty_stmt_index(stmts)
if last_idx < 0 {
return
}
for i, stmt in stmts {
if stmt is ast.EmptyStmt {
continue
}
if i == last_idx && stmt is ast.ExprStmt {
if stmt.expr is ast.IfExpr {
nested_if := stmt.expr as ast.IfExpr
if !g.if_expr_can_be_ternary(&nested_if) && nested_if.else_expr !is ast.EmptyExpr {
g.gen_decl_if_expr(name, value_type, &nested_if)
continue
}
if nested_if.else_expr is ast.EmptyExpr {
if payload_expr := g.unsafe_expr_or_payload_value(stmts) {
g.gen_stmt(stmt)
g.gen_if_expr_assign_value(name, value_type, payload_expr)
continue
}
}
}
// When the last expression is a void call (e.g. array__push from <<),
// emit it as a standalone statement without assigning to the temp var.
if g.expr_is_void_call(stmt.expr) {
g.write_indent()
g.expr(stmt.expr)
g.sb.writeln(';')
} else {
g.gen_if_expr_assign_value(name, value_type, stmt.expr)
}
} else {
g.gen_stmt(stmt)
}
}
}
fn (mut g Gen) gen_scoped_decl_if_expr_branch(name string, value_type string, stmts []ast.Stmt) {
saved_runtime_local_types := g.runtime_local_types.clone()
saved_runtime_decl_types := g.runtime_decl_types.clone()
saved_not_local_var_cache := g.not_local_var_cache.clone()
g.gen_decl_if_expr_branch(name, value_type, stmts)
g.runtime_local_types = saved_runtime_local_types.clone()
g.runtime_decl_types = saved_runtime_decl_types.clone()
g.not_local_var_cache = saved_not_local_var_cache.clone()
}
// expr_is_void_call checks if the expression is a function call that returns void.
// Used to prevent assigning void results to temp variables in if-expression branches.
fn (mut g Gen) expr_is_void_call(expr ast.Expr) bool {
if expr is ast.CallExpr {
if ret := g.get_call_return_type(expr.lhs, expr.args) {
return ret == 'void'
}
// Check resolved name
c_name := g.resolve_call_name(expr.lhs, expr.args.len)
if c_name != '' {
if ret := g.fn_return_types[c_name] {
return ret == 'void'
}
}
// panic/v_panic always returns void (noreturn)
if expr.lhs is ast.Ident {
if expr.lhs.name in ['panic', 'v_panic'] {
return true
}
}
}
return false
}
fn (mut g Gen) gen_decl_if_expr(name string, value_type string, if_expr &ast.IfExpr) {
if if_expr.cond is ast.EmptyExpr {
g.gen_scoped_decl_if_expr_branch(name, value_type, if_expr.stmts)
return
}
g.write_indent()
g.sb.write_string('if (')
g.expr(if_expr.cond)
g.sb.writeln(') {')
g.indent++
g.gen_scoped_decl_if_expr_branch(name, value_type, if_expr.stmts)
g.indent--
g.write_indent()
g.sb.write_string('}')
if if_expr.else_expr is ast.IfExpr {
else_if := if_expr.else_expr as ast.IfExpr
if else_if.cond is ast.EmptyExpr {
g.sb.writeln(' else {')
g.indent++
g.gen_scoped_decl_if_expr_branch(name, value_type, else_if.stmts)
g.indent--
g.write_indent()
g.sb.writeln('}')
} else {
g.sb.writeln(' else {')
g.indent++
g.gen_decl_if_expr(name, value_type, &else_if)
g.indent--
g.write_indent()
g.sb.writeln('}')
}
} else if if_expr.else_expr !is ast.EmptyExpr {
g.sb.writeln(' else {')
g.indent++
g.gen_if_expr_assign_value(name, value_type, if_expr.else_expr)
g.indent--
g.write_indent()
g.sb.writeln('}')
} else {
g.sb.writeln('')
}
}
fn (mut g Gen) gen_return_if_branch(stmts []ast.Stmt) {
if stmts.len == 0 {
return
}
for i, stmt in stmts {
if i == stmts.len - 1 && stmt is ast.ExprStmt {
if stmt.expr is ast.IfExpr {
nested_if := stmt.expr as ast.IfExpr
if !g.if_expr_can_be_ternary(&nested_if) && nested_if.else_expr !is ast.EmptyExpr {
g.gen_return_if_expr(nested_if, true)
continue
}
}
mut ret_exprs := []ast.Expr{cap: 1}
ret_exprs << stmt.expr
ret_stmt := ast.ReturnStmt{
exprs: ret_exprs
}
prev_suppress := g.suppress_return_drop_emit
g.suppress_return_drop_emit = true
g.gen_stmt(ret_stmt)
g.suppress_return_drop_emit = prev_suppress
} else {
g.gen_stmt(stmt)
}
}
}
fn (mut g Gen) gen_return_if_expr(if_expr ast.IfExpr, emit_indent bool) {
if if_expr.cond is ast.EmptyExpr {
g.gen_return_if_branch(if_expr.stmts)
return
}
if emit_indent {
g.write_indent()
}
g.sb.write_string('if (')
g.expr(if_expr.cond)
g.sb.writeln(') {')
g.indent++
g.gen_return_if_branch(if_expr.stmts)
g.indent--
g.write_indent()
g.sb.write_string('}')
if if_expr.else_expr is ast.IfExpr {
else_if := if_expr.else_expr as ast.IfExpr
if else_if.cond is ast.EmptyExpr {
g.sb.writeln(' else {')
g.indent++
g.gen_return_if_branch(else_if.stmts)
g.indent--
g.write_indent()
g.sb.writeln('}')
} else {
g.sb.writeln(' else {')
g.indent++
g.gen_return_if_expr(else_if, true)
g.indent--
g.write_indent()
g.sb.writeln('}')
}
} else if if_expr.else_expr !is ast.EmptyExpr {
g.sb.writeln(' else {')
g.indent++
mut else_exprs := []ast.Expr{cap: 1}
else_exprs << if_expr.else_expr
else_stmt := ast.ReturnStmt{
exprs: else_exprs
}
prev_suppress := g.suppress_return_drop_emit
g.suppress_return_drop_emit = true
g.gen_stmt(else_stmt)
g.suppress_return_drop_emit = prev_suppress
g.indent--
g.write_indent()
g.sb.writeln('}')
} else {
g.sb.writeln('')
}
}
fn (g &Gen) if_expr_can_be_ternary(node &ast.IfExpr) bool {
if node.cond !is ast.EmptyExpr {
if node.stmts.len != 1 || node.stmts[0] !is ast.ExprStmt {
return false
}
if node.else_expr is ast.EmptyExpr {
return false
}
}
if node.cond is ast.EmptyExpr {
return node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt
}
if node.else_expr is ast.IfExpr {
else_if := node.else_expr as ast.IfExpr
return g.if_expr_can_be_ternary(&else_if)
}
return true
}
fn (g &Gen) extract_if_expr(expr ast.Expr) ?ast.IfExpr {
match expr {
ast.IfExpr {
return expr
}
ast.ParenExpr {
return g.extract_if_expr(expr.expr)
}
ast.ModifierExpr {
return g.extract_if_expr(expr.expr)
}
ast.UnsafeExpr {
if expr.stmts.len == 1 {
stmt0 := expr.stmts[0]
if stmt0 is ast.ExprStmt {
expr_stmt := stmt0 as ast.ExprStmt
return g.extract_if_expr(expr_stmt.expr)
}
}
return none
}
else {
return none
}
}
}
fn last_non_empty_stmt_index(stmts []ast.Stmt) int {
mut idx := stmts.len - 1
for idx >= 0 && stmts[idx] is ast.EmptyStmt {
idx--
}
return idx
}
fn (mut g Gen) branch_result_type(stmts []ast.Stmt) string {
last_idx := last_non_empty_stmt_index(stmts)
if last_idx < 0 {
return ''
}
last := stmts[last_idx]
match last {
ast.BlockStmt {
return g.branch_result_type(last.stmts)
}
ast.ExprStmt {
if nested := g.extract_if_expr(last.expr) {
return g.get_if_expr_type(&nested)
}
mut typ := g.get_expr_type(last.expr)
if (typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr')
&& last.expr is ast.Ident {
if local_typ := g.branch_declared_ident_type(stmts, last_idx, last.expr.name) {
typ = local_typ
}
}
if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' {
if raw := g.get_raw_type(last.expr) {
raw_type := g.types_type_to_c(raw)
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
typ = raw_type
}
}
}
return typ
}
else {
return ''
}
}
}
fn (mut g Gen) branch_declared_ident_type(stmts []ast.Stmt, before_idx int, name string) ?string {
if name == '' {
return none
}
mut i := before_idx - 1
for i >= 0 {
stmt := stmts[i]
if stmt is ast.AssignStmt {
if stmt.op == .decl_assign && stmt.lhs.len == stmt.rhs.len {
for j, lhs in stmt.lhs {
if lhs is ast.Ident && lhs.name == name {
mut typ := g.get_expr_type(stmt.rhs[j])
if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' {
if raw := g.get_raw_type(stmt.rhs[j]) {
raw_type := g.types_type_to_c(raw)
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
typ = raw_type
}
}
}
if typ != '' && typ != 'int' && typ != 'void*' && typ != 'voidptr' {
return typ
}
}
}
}
}
i--
}
return none
}
fn (mut g Gen) gen_if_expr_stmt(node &ast.IfExpr) {
// Skip empty conditions (pure else blocks shouldn't appear at top level)
if node.cond is ast.EmptyExpr {
return
}
if cond_value := const_bool_value(node.cond) {
if cond_value {
g.gen_scoped_stmts(node.stmts)
return
}
if node.else_expr is ast.IfExpr {
else_if := node.else_expr as ast.IfExpr
if else_if.cond is ast.EmptyExpr {
g.gen_scoped_stmts(else_if.stmts)
} else {
g.gen_if_expr_stmt(&else_if)
}
} else if node.else_expr !is ast.EmptyExpr {
g.gen_scoped_expr_stmts(node.else_expr)
}
return
}
g.sb.write_string('if (')
g.expr(node.cond)
g.sb.writeln(') {')
g.indent++
g.gen_scoped_stmts(node.stmts)
g.indent--
g.write_indent()
g.sb.write_string('}')
// Handle else / else-if
if node.else_expr !is ast.EmptyExpr {
if node.else_expr is ast.IfExpr {
else_if := node.else_expr as ast.IfExpr
if else_if.cond is ast.EmptyExpr {
g.sb.writeln(' else {')
g.indent++
g.gen_scoped_stmts(else_if.stmts)
g.indent--
g.write_indent()
g.sb.write_string('}')
} else {
g.sb.write_string(' else ')
g.gen_if_expr_stmt(&else_if)
}
} else {
g.sb.writeln(' else {')
g.indent++
g.gen_scoped_expr_stmts(node.else_expr)
g.indent--
g.write_indent()
g.sb.write_string('}')
}
}
g.sb.writeln('')
}
fn (mut g Gen) gen_if_expr_ternary(node &ast.IfExpr) {
if node.cond is ast.EmptyExpr {
if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt {
stmt := node.stmts[0] as ast.ExprStmt
g.expr(stmt.expr)
} else {
g.sb.write_string('0')
}
return
}
g.sb.write_string('(')
g.expr(node.cond)
g.sb.write_string(' ? ')
if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt {
stmt := node.stmts[0] as ast.ExprStmt
if nested := g.extract_if_expr(stmt.expr) {
g.gen_if_expr_value(&nested)
} else {
g.expr(stmt.expr)
}
} else {
g.sb.write_string('0')
}
g.sb.write_string(' : ')
if node.else_expr is ast.IfExpr {
else_if := node.else_expr as ast.IfExpr
g.gen_if_expr_value(&else_if)
} else if node.else_expr is ast.EmptyExpr {
g.sb.write_string('0')
} else {
if nested := g.extract_if_expr(node.else_expr) {
g.gen_if_expr_value(&nested)
} else {
g.expr(node.else_expr)
}
}
g.sb.write_string(')')
}
fn (mut g Gen) gen_if_expr_value(node &ast.IfExpr) {
mut value_type := g.get_if_expr_type(node)
if value_type == '' || value_type == 'void' {
g.sb.write_string('({ ')
g.gen_if_expr_stmt(node)
g.sb.write_string('; 0; })')
return
}
if value_type == 'int_literal' {
value_type = 'int'
}
tmp_name := '_if_expr_t${g.tmp_counter}'
g.tmp_counter++
g.sb.write_string('({ ${value_type} ${tmp_name} = ${zero_value_for_type(value_type)}; ')
g.gen_decl_if_expr(tmp_name, value_type, node)
if value_type.starts_with('Array_fixed_') && !value_type.ends_with('*') {
_, fixed_len := parse_fixed_array_elem_type(value_type)
if fixed_len > 0 {
g.sb.write_string(' (${value_type}){')
for i in 0 .. fixed_len {
if i > 0 {
g.sb.write_string(', ')
}
g.sb.write_string('${tmp_name}[${i}]')
}
g.sb.write_string('}; })')
return
}
}
g.sb.write_string(' ${tmp_name}; })')
}
fn (mut g Gen) get_if_expr_type(node &ast.IfExpr) string {
mut env_type := ''
if t := g.get_expr_type_from_env(ast.Expr(*node)) {
if t != '' {
env_type = t
}
}
mut branch_type := g.branch_result_type(node.stmts)
if branch_type == 'int_literal' {
branch_type = 'int'
}
mut else_type := ''
if node.else_expr is ast.IfExpr {
else_if := node.else_expr as ast.IfExpr
else_type = g.get_if_expr_type(&else_if)
} else if node.else_expr !is ast.EmptyExpr {
if nested := g.extract_if_expr(node.else_expr) {
else_type = g.get_if_expr_type(&nested)
} else {
else_type = g.get_expr_type(node.else_expr)
if else_type == '' || else_type == 'int' || else_type == 'void*'
|| else_type == 'voidptr' {
if raw := g.get_raw_type(node.else_expr) {
raw_type := g.types_type_to_c(raw)
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
else_type = raw_type
}
}
}
}
}
if else_type == 'int_literal' {
else_type = 'int'
}
if branch_type != '' && else_type != '' && branch_type == else_type {
if branch_type != 'int' || env_type == '' || env_type == 'int' || env_type == 'void*'
|| env_type == 'voidptr' {
return branch_type
}
}
if branch_type != '' && branch_type != 'int' {
if env_type == '' || env_type == 'int' || branch_type.starts_with('${env_type}_T_') {
return branch_type
}
}
if env_type != '' {
if g.cur_fn_ret_type.starts_with('${env_type}_T_') {
return g.cur_fn_ret_type
}
return env_type
}
if else_type != '' {
return else_type
}
return 'int'
}
// expr_produces_pointer checks if an expression produces a pointer value in C.
// More comprehensive than expr_is_pointer - handles casts, pointer arithmetic, etc.
// Used in gen_call_arg to avoid wrapping pointer expressions in compound literals.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,17 @@
// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module cleanc
import os
fn (g &Gen) print_cgen_mem(stage string) {
if os.getenv('V2_MEM') == '' {
return
}
$if macos {
eprintln(' [mem] cleanc/${stage}: live ${darwin_cleanc_live_mb()} MB')
} $else {
eprintln(' [mem] cleanc/${stage}')
}
}
@@ -0,0 +1,22 @@
// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module cleanc
#include <malloc/malloc.h>
struct C.malloc_statistics_t {
blocks_in_use u32
size_in_use usize
max_size_in_use usize
size_allocated usize
}
fn C.malloc_default_zone() voidptr
fn C.malloc_zone_statistics(zone voidptr, stats &C.malloc_statistics_t)
fn darwin_cleanc_live_mb() u64 {
mut st := C.malloc_statistics_t{}
C.malloc_zone_statistics(C.malloc_default_zone(), &st)
return u64(st.size_in_use) / (1024 * 1024)
}
@@ -0,0 +1,275 @@
module cleanc
import os
import v2.parser
import v2.pref as vpref
import v2.token
import v2.transformer
import v2.types
fn module_storage_csrc_for_test_sources(sources map[string]string) string {
tmp_dir := os.join_path(os.temp_dir(), 'v2_module_storage_codegen_${os.getpid()}')
os.rmdir_all(tmp_dir) or {}
os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
defer {
os.rmdir_all(tmp_dir) or {}
}
mut paths := []string{}
for rel_path, code in sources {
tmp_file := os.join_path(tmp_dir, rel_path)
os.mkdir_all(os.dir(tmp_file)) or { panic('failed to create temp source dir') }
os.write_file(tmp_file, code) or { panic('failed to write temp source') }
paths << tmp_file
}
prefs := &vpref.Preferences{
backend: .cleanc
no_parallel: true
}
mut file_set := token.FileSet.new()
mut par := parser.Parser.new(prefs)
files := par.parse_files(paths, mut file_set)
env := types.Environment.new()
mut checker := types.Checker.new(prefs, file_set, env)
checker.check_files(files)
mut trans := transformer.Transformer.new_with_pref(env, prefs)
trans.set_file_set(file_set)
transformed_files := trans.transform_files(files)
mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs)
return gen.gen()
}
fn test_generate_c_uses_module_prefix_for_qualified_module_storage() {
csrc := module_storage_csrc_for_test_sources({
'report/report.v': 'module report
pub __global mut errors = 0
'
'main.v': 'module main
import report
fn main() {
report.errors += 1
}
'
})
assert csrc.contains('int report__errors'), csrc
assert csrc.contains('report__errors'), csrc
assert !csrc.contains('\nint errors'), csrc
}
fn test_generate_c_resolves_module_storage_import_alias() {
csrc := module_storage_csrc_for_test_sources({
'report/report.v': 'module report
pub __global mut errors = 0
'
'main.v': 'module main
import report as r
fn main() {
r.errors += 1
}
'
})
assert csrc.contains('report__errors'), csrc
assert !csrc.contains('r__errors'), csrc
}
fn test_generate_c_keeps_same_short_module_storage_names_distinct() {
csrc := module_storage_csrc_for_test_sources({
'a/a.v': 'module a
pub __global state = 1
'
'b/b.v': 'module b
pub __global state = 2
'
'main.v': 'module main
import a
import b
fn main() {
_ = a.state + b.state
}
'
})
assert csrc.contains('int a__state = 1;'), csrc
assert csrc.contains('int b__state = 2;'), csrc
assert !csrc.contains('\nint state ='), csrc
}
fn test_generate_c_prefixes_module_storage_names_containing_dunders() {
csrc := module_storage_csrc_for_test_sources({
'a/a.v': 'module a
pub __global mut state__x = 1
pub fn bump() int {
state__x += 1
return state__x
}
'
'b/b.v': 'module b
pub __global mut state__x = 2
pub fn bump() int {
state__x += 1
return state__x
}
'
'main.v': 'module main
import a
import b
fn main() {
_ = a.bump() + b.bump()
_ = a.state__x + b.state__x
}
'
})
assert csrc.contains('int a__state__x = 1;'), csrc
assert csrc.contains('int b__state__x = 2;'), csrc
assert csrc.contains('a__state__x += 1;'), csrc
assert csrc.contains('return a__state__x;'), csrc
assert csrc.contains('b__state__x += 1;'), csrc
assert csrc.contains('return b__state__x;'), csrc
assert !csrc.contains('\nint state__x ='), csrc
assert !csrc.contains('\n\tstate__x += 1;'), csrc
assert !csrc.contains('\n\treturn state__x;'), csrc
}
fn test_generate_c_keeps_c_global_declaration_unmangled() {
csrc := module_storage_csrc_for_test_sources({
'main.v': 'module main
@[c_extern]
__global C.errno int
@[c_extern]
__global C.stdin voidptr
@[c_extern]
__global C.stdout voidptr
@[c_extern]
__global C.stderr voidptr
fn main() {
_ = C.errno
_ = C.stdin
_ = C.stdout
_ = C.stderr
}
'
})
assert !csrc.contains('main__C.errno'), csrc
assert !csrc.contains('main__errno'), csrc
assert !csrc.contains('main__stdin'), csrc
assert !csrc.contains('main__stdout'), csrc
assert !csrc.contains('main__stderr'), csrc
assert csrc.contains('errno'), csrc
assert csrc.contains('stdin'), csrc
assert csrc.contains('stdout'), csrc
assert csrc.contains('stderr'), csrc
}
fn test_generate_c_keeps_c_extern_global_attributes_unmangled() {
csrc := module_storage_csrc_for_test_sources({
'main.v': 'module main
@[c_extern; export: "raw_counter"; weak; hidden; markused]
__global raw_counter int
fn main() {
_ = raw_counter
}
'
})
assert csrc.contains('extern int raw_counter;'), csrc
assert csrc.contains('raw_counter'), csrc
assert !csrc.contains('main__raw_counter'), csrc
}
fn test_generate_c_keeps_translated_c_v_extern_global_unmangled() {
csrc := module_storage_csrc_for_test_sources({
'cwrap/runtime.c.v': '@[translated]
module cwrap
@[c_extern; hidden; markused]
__global raw_status int
pub fn read_status() int {
return raw_status
}
'
'main.v': 'module main
import cwrap
fn main() {
_ = cwrap.read_status()
_ = cwrap.raw_status
}
'
})
assert csrc.contains('extern int raw_status;'), csrc
assert csrc.contains('return raw_status;'), csrc
assert csrc.count('raw_status') >= 3, csrc
assert !csrc.contains('cwrap__raw_status'), csrc
}
fn test_generate_c_keeps_qualified_c_extern_global_alias_unmangled() {
csrc := module_storage_csrc_for_test_sources({
'cwrap/runtime.c.v': '@[translated]
module cwrap
@[c_extern; hidden; markused]
__global raw_status int
'
'main.v': 'module main
import cwrap as cw
fn main() {
_ = cw.raw_status
}
'
})
assert csrc.contains('extern int raw_status;'), csrc
assert csrc.count('raw_status') >= 2, csrc
assert !csrc.contains('cwrap__raw_status'), csrc
assert !csrc.contains('cw__raw_status'), csrc
}
fn test_generate_c_keeps_plain_module_c_extern_global_unmangled() {
csrc := module_storage_csrc_for_test_sources({
'ext/ext.v': 'module ext
@[c_extern]
pub __global raw_status int
pub fn read_status() int {
return raw_status
}
'
'main.v': 'module main
import ext
fn main() {
_ = ext.raw_status
_ = ext.read_status()
}
'
})
assert csrc.contains('extern int raw_status;'), csrc
assert csrc.contains('return raw_status;'), csrc
assert csrc.contains('(void)(raw_status);'), csrc
assert !csrc.contains('ext__raw_status'), csrc
}
@@ -0,0 +1,374 @@
// 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 cleanc
import v2.ast
fn (mut g Gen) try_gen_orm_create_call(c_name string, call_args []ast.Expr) bool {
if call_args.len != 3 || !c_name.ends_with('__create') {
return false
}
if !orm_table_can_emit(call_args[1]) || !orm_table_fields_array_can_emit(call_args[2]) {
return false
}
g.sb.write_string('${c_name}(')
g.gen_call_arg(c_name, 0, call_args[0])
g.sb.write_string(', ')
g.write_orm_table_c_expr(call_args[1])
g.sb.write_string(', ')
g.write_orm_table_fields_array_c_expr(call_args[2])
g.sb.write_string(')')
if c_name != '' {
g.mark_called_fn_name(c_name)
}
return true
}
fn (mut g Gen) try_gen_orm_create_call_expr(node ast.CallExpr) bool {
if !orm_create_call_can_emit(node) {
return false
}
lhs := node.lhs as ast.SelectorExpr
name := g.orm_create_call_name(node) or { return false }
call_args := [
lhs.lhs,
node.args[0],
node.args[1],
]
return g.try_gen_orm_create_call(name, call_args)
}
fn orm_create_call_can_emit(node ast.CallExpr) bool {
if node.args.len != 2 || node.lhs !is ast.SelectorExpr {
return false
}
lhs := node.lhs as ast.SelectorExpr
return lhs.rhs.name == 'create' && orm_table_can_emit(node.args[0])
&& orm_table_fields_array_can_emit(node.args[1])
}
fn (mut g Gen) orm_create_call_name(node ast.CallExpr) ?string {
if node.lhs !is ast.SelectorExpr {
return none
}
lhs := node.lhs as ast.SelectorExpr
method_name := sanitize_fn_ident(lhs.rhs.name)
mut name := g.resolve_call_name(node.lhs, node.args.len)
if name != '' {
return name
}
mut base_type := g.method_receiver_base_type(lhs.lhs)
if base_type == '' {
base_type = g.get_expr_type(lhs.lhs).trim_space().trim_right('*')
}
if base_type == '' {
base_type = g.receiver_local_base_type(lhs.lhs)
}
if name == '' && base_type != '' {
if embedded := g.resolve_method_on_embedded_receiver(base_type, method_name) {
name = embedded.method_c_name
}
}
if name == '' && base_type != '' {
name = '${base_type}__${method_name}'
}
if name == '' {
return none
}
return name
}
fn (mut g Gen) orm_create_call_result_type(node ast.CallExpr) ?string {
if !orm_create_call_can_emit(node) {
return none
}
name := g.orm_create_call_name(node) or { return none }
if ret := g.fn_return_types[name] {
return ret
}
return none
}
fn orm_table_can_emit(expr ast.Expr) bool {
init := orm_init_expr(expr) or { return false }
name_expr := orm_init_field_value(init, 'name') or { return false }
attrs_expr := orm_init_field_value(init, 'attrs') or { return false }
return orm_string_value(name_expr) != none && orm_vattribute_array_can_emit(attrs_expr)
}
fn orm_table_fields_array_can_emit(expr ast.Expr) bool {
arr := orm_array_init_expr(expr) or { return false }
for field_expr in arr.exprs {
if !orm_table_field_can_emit(field_expr) {
return false
}
}
return true
}
fn orm_table_field_can_emit(expr ast.Expr) bool {
init := orm_init_expr(expr) or { return false }
name_expr := orm_init_field_value(init, 'name') or { return false }
typ_expr := orm_init_field_value(init, 'typ') or { return false }
nullable_expr := orm_init_field_value(init, 'nullable') or { return false }
default_val_expr := orm_init_field_value(init, 'default_val') or { return false }
attrs_expr := orm_init_field_value(init, 'attrs') or { return false }
is_arr_expr := orm_init_field_value(init, 'is_arr') or { return false }
return orm_string_value(name_expr) != none && orm_number_c_expr(typ_expr) != none
&& orm_bool_c_expr(nullable_expr) != none && orm_string_value(default_val_expr) != none
&& orm_vattribute_array_can_emit(attrs_expr) && orm_bool_c_expr(is_arr_expr) != none
}
fn orm_vattribute_array_can_emit(expr ast.Expr) bool {
arr := orm_array_init_expr(expr) or { return false }
for attr_expr in arr.exprs {
if !orm_vattribute_can_emit(attr_expr) {
return false
}
}
return true
}
fn orm_vattribute_can_emit(expr ast.Expr) bool {
init := orm_init_expr(expr) or { return false }
name_expr := orm_init_field_value(init, 'name') or { return false }
has_arg_expr := orm_init_field_value(init, 'has_arg') or { return false }
arg_expr := orm_init_field_value(init, 'arg') or { return false }
kind_expr := orm_init_field_value(init, 'kind') or { return false }
return orm_string_value(name_expr) != none && orm_bool_c_expr(has_arg_expr) != none
&& orm_string_value(arg_expr) != none && orm_ident_c_expr(kind_expr) != none
}
fn (mut g Gen) write_orm_table_c_expr(expr ast.Expr) {
init := orm_init_expr(expr) or { return }
name_expr := orm_init_field_value(init, 'name') or { return }
attrs_expr := orm_init_field_value(init, 'attrs') or { return }
g.sb.write_string('((orm__Table){.name = ')
g.write_orm_v_string_c_expr(name_expr)
g.sb.write_string(',.attrs = ')
g.write_orm_vattribute_array_c_expr(attrs_expr)
g.sb.write_string('})')
}
fn (mut g Gen) write_orm_table_fields_array_c_expr(expr ast.Expr) {
arr := orm_array_init_expr(expr) or { return }
g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(orm__TableField), ')
if arr.exprs.len == 0 {
g.sb.write_string('&(array){0})')
return
}
g.sb.write_string('&(orm__TableField[${arr.exprs.len}]){')
for i, field_expr in arr.exprs {
if i > 0 {
g.sb.write_string(', ')
}
g.write_orm_table_field_c_expr(field_expr)
}
g.sb.write_string('})')
}
fn (mut g Gen) write_orm_table_field_c_expr(expr ast.Expr) {
init := orm_init_expr(expr) or { return }
name_expr := orm_init_field_value(init, 'name') or { return }
typ_expr := orm_init_field_value(init, 'typ') or { return }
nullable_expr := orm_init_field_value(init, 'nullable') or { return }
default_val_expr := orm_init_field_value(init, 'default_val') or { return }
attrs_expr := orm_init_field_value(init, 'attrs') or { return }
is_arr_expr := orm_init_field_value(init, 'is_arr') or { return }
g.sb.write_string('((orm__TableField){.name = ')
g.write_orm_v_string_c_expr(name_expr)
g.sb.write_string(',.typ = ')
g.sb.write_string(orm_number_c_expr(typ_expr) or { return })
g.sb.write_string(',.nullable = ')
g.sb.write_string(orm_bool_c_expr(nullable_expr) or { return })
g.sb.write_string(',.default_val = ')
g.write_orm_v_string_c_expr(default_val_expr)
g.sb.write_string(',.attrs = ')
g.write_orm_vattribute_array_c_expr(attrs_expr)
g.sb.write_string(',.is_arr = ')
g.sb.write_string(orm_bool_c_expr(is_arr_expr) or { return })
g.sb.write_string('})')
}
fn (mut g Gen) write_orm_vattribute_array_c_expr(expr ast.Expr) {
arr := orm_array_init_expr(expr) or { return }
if arr.exprs.len == 0 {
g.sb.write_string('new_array_from_c_array(0, 0, sizeof(VAttribute), &(array){0})')
return
}
g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(VAttribute), &(VAttribute[${arr.exprs.len}]){')
for i, attr_expr in arr.exprs {
if i > 0 {
g.sb.write_string(', ')
}
g.write_orm_vattribute_c_expr(attr_expr)
}
g.sb.write_string('})')
}
fn (mut g Gen) write_orm_vattribute_c_expr(expr ast.Expr) {
init := orm_init_expr(expr) or { return }
name_expr := orm_init_field_value(init, 'name') or { return }
has_arg_expr := orm_init_field_value(init, 'has_arg') or { return }
arg_expr := orm_init_field_value(init, 'arg') or { return }
kind_expr := orm_init_field_value(init, 'kind') or { return }
g.sb.write_string('((VAttribute){.name = ')
g.write_orm_v_string_c_expr(name_expr)
g.sb.write_string(',.has_arg = ')
g.sb.write_string(orm_bool_c_expr(has_arg_expr) or { return })
g.sb.write_string(',.arg = ')
g.write_orm_v_string_c_expr(arg_expr)
g.sb.write_string(',.kind = ')
g.sb.write_string(orm_ident_c_expr(kind_expr) or { return })
g.sb.write_string('})')
}
fn orm_init_expr(expr ast.Expr) ?ast.InitExpr {
match expr {
ast.InitExpr {
return expr
}
ast.ParenExpr {
return orm_init_expr(expr.expr)
}
else {}
}
return none
}
fn orm_array_init_expr(expr ast.Expr) ?ast.ArrayInitExpr {
match expr {
ast.ArrayInitExpr {
return expr
}
ast.CallExpr {
name := orm_call_name(expr.lhs)
if name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array', 'new_array_from_c_array']
&& expr.args.len >= 4 {
return orm_array_init_expr(expr.args[3])
}
}
ast.PrefixExpr {
if expr.op == .amp {
return orm_array_init_expr(expr.expr)
}
}
ast.ParenExpr {
return orm_array_init_expr(expr.expr)
}
else {}
}
return none
}
fn orm_call_name(lhs ast.Expr) string {
match lhs {
ast.Ident {
return lhs.name
}
ast.SelectorExpr {
if lhs.lhs is ast.Ident {
left := lhs.lhs as ast.Ident
return '${left.name}__${lhs.rhs.name}'
}
}
else {}
}
return ''
}
fn orm_init_field_value(init ast.InitExpr, name string) ?ast.Expr {
for field in init.fields {
if field.name == name {
return field.value
}
}
return none
}
fn (mut g Gen) write_orm_v_string_c_expr(expr ast.Expr) {
raw := orm_string_value(expr) or { return }
g.sb.write_string(c_static_v_string_expr_from_c_literal(c_string_literal_content_to_c(raw)))
}
fn orm_string_value(expr ast.Expr) ?string {
match expr {
ast.StringLiteral {
mut val := strip_literal_quotes(expr.value)
if expr.kind == .raw {
val = val.replace('\\', '\\\\')
} else {
val = process_line_continuations(val)
}
return val
}
ast.BasicLiteral {
if expr.kind == .string {
return process_line_continuations(strip_literal_quotes(expr.value))
}
}
else {}
}
return none
}
fn orm_bool_c_expr(expr ast.Expr) ?string {
match expr {
ast.BasicLiteral {
if expr.kind == .key_true || expr.value == 'true' {
return 'true'
}
if expr.kind == .key_false || expr.value == 'false' {
return 'false'
}
}
ast.Ident {
if expr.name == 'true' || expr.name == 'false' {
return expr.name
}
}
else {}
}
return none
}
fn orm_number_c_expr(expr ast.Expr) ?string {
match expr {
ast.BasicLiteral {
return sanitize_c_number_literal(expr.value)
}
ast.PrefixExpr {
if expr.op == .minus && expr.expr is ast.BasicLiteral {
inner := expr.expr as ast.BasicLiteral
return '-' + sanitize_c_number_literal(inner.value)
}
}
else {}
}
return none
}
fn orm_ident_c_expr(expr ast.Expr) ?string {
match expr {
ast.Ident {
return expr.name
}
ast.SelectorExpr {
if expr.lhs is ast.Ident {
left := expr.lhs as ast.Ident
return '${left.name}__${expr.rhs.name}'
}
}
else {}
}
return none
}
@@ -0,0 +1,51 @@
// vtest build: !linux && !windows
module cleanc
import os
import v2.markused
import v2.parser
import v2.pref as vpref
import v2.token
import v2.transformer
import v2.types
fn generate_parallel_worker_c_with_cached_init_for_test(code string, cached_init_calls []string) string {
tmp_file := os.join_path(os.temp_dir(), 'v2_parallel_cache_init_test_${os.getpid()}.v')
os.write_file(tmp_file, code) or { panic('failed to write temp file') }
defer {
os.rm(tmp_file) or {}
}
prefs := &vpref.Preferences{
backend: .cleanc
no_parallel: false
}
mut file_set := token.FileSet.new()
mut par := parser.Parser.new(prefs)
files := par.parse_files([tmp_file], mut file_set)
env := types.Environment.new()
mut checker := types.Checker.new(prefs, file_set, env)
checker.check_files(files)
mut trans := transformer.Transformer.new_with_pref(env, prefs)
gen_files := trans.transform_files(files)
used := markused.mark_used(gen_files, env)
mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs)
gen.set_used_fn_keys(used)
gen.set_cached_init_calls(cached_init_calls)
gen.gen_passes_1_to_4()
emit_indices := gen.gen_pass5_pre()
mut worker := gen.new_pass5_worker(emit_indices, 0)
worker.gen_pass5_files(emit_indices)
gen.merge_pass5_worker(worker)
gen.gen_pass5_post()
return gen.gen_finalize()
}
fn test_parallel_worker_preserves_cached_init_calls_in_main() {
csrc := generate_parallel_worker_c_with_cached_init_for_test('
fn main() {
}
', [
'__v2_cached_init_imports',
])
assert csrc.contains('__v2_cached_init_imports();')
}
@@ -0,0 +1,54 @@
module cleanc
fn test_pass5_worker_clones_global_type_lookup() {
mut g := Gen.new([])
g.global_var_types['main__settings'] = 'main__Settings'
mut worker := g.new_pass5_worker([], 0)
assert worker.global_var_types['main__settings'] == 'main__Settings'
g.global_var_types['main__settings'] = 'main__OtherSettings'
assert worker.global_var_types['main__settings'] == 'main__Settings'
}
fn test_specialized_receiver_method_index_tracks_ambiguity() {
mut g := Gen.new([])
g.fn_return_types['Box_T_i32__get'] = 'int'
g.remember_specialized_fn_base('Box_T_i32__get')
got := g.resolve_specialized_receiver_method('Box', 'get') or { '' }
assert got == 'Box_T_i32__get'
g.fn_return_types['Box_T_string__get'] = 'string'
g.remember_specialized_fn_base('Box_T_string__get')
if _ := g.resolve_specialized_receiver_method('Box', 'get') {
assert false
} else {
assert true
}
}
// When a file is split across pass-5 workers, a non-owning worker blocks the
// file's fns but must still emit its own assigned slice. The owner-scoped bypass
// allows exactly the fns owned by the slice's file (explicit_slice_file) and
// nothing transitively reached from another file.
fn test_explicit_slice_emit_allows_is_scoped_to_owning_file() {
mut g := Gen.new([])
g.fn_owner_file['fn_foo'] = 3 // foo is owned by file 3
g.fn_owner_file['fn_bar'] = 5 // bar is owned by a different file
g.blocked_fn_keys['fn_foo'] = true
g.blocked_fn_keys['fn_bar'] = true
// Not in an explicit slice: nothing is unblocked.
assert !g.explicit_slice_emit_allows('fn_foo')
assert !g.explicit_slice_emit_allows('fn_bar')
// Emitting file 3's slice: only file 3's fns are unblocked.
g.explicit_slice_active = true
g.explicit_slice_file = 3
assert g.explicit_slice_emit_allows('fn_foo')
assert !g.explicit_slice_emit_allows('fn_bar') // owned by file 5 -> stays blocked
assert !g.explicit_slice_emit_allows('fn_unknown') // no recorded owner -> blocked
// Switching to file 5's slice flips which fn is allowed.
g.explicit_slice_file = 5
assert !g.explicit_slice_emit_allows('fn_foo')
assert g.explicit_slice_emit_allows('fn_bar')
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,148 @@
// 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.
// Structure of Arrays (SoA) code generation.
//
// When a struct is annotated with @[soa], the compiler generates a companion
// SoA container struct that stores each field in a separate contiguous array.
// This layout provides significantly better cache performance for batch
// operations that touch only a subset of fields (common in game math, ECS,
// particle systems, physics simulations, etc.).
//
// Example:
// @[soa]
// struct Particle {
// x f32
// y f32
// vx f32
// vy f32
// }
//
// Generates a companion type `Particle_SOA` with:
// struct Particle_SOA {
// int len;
// int cap;
// f32* x;
// f32* y;
// f32* vx;
// f32* vy;
// };
//
// And helper functions:
// Particle_SOA Particle_SOA_new(int len, int cap);
// void Particle_SOA_push(Particle_SOA* soa, Particle val);
// Particle Particle_SOA_get(Particle_SOA soa, int i);
// void Particle_SOA_set(Particle_SOA* soa, int i, Particle val);
// void Particle_SOA_free(Particle_SOA* soa);
module cleanc
import v2.types
// gen_soa_companion generates the SoA container struct and helper functions
// for a struct annotated with @[soa].
fn (mut g Gen) gen_soa_companion(name string, s types.Struct) {
soa_name := '${name}_SOA'
// --- SoA container struct ---
g.sb.writeln('// SoA (Structure of Arrays) companion for ${name}')
g.sb.writeln('typedef struct {')
g.sb.writeln('\tint len;')
g.sb.writeln('\tint cap;')
for field in s.fields {
c_type := g.types_type_to_c(field.typ)
fname := escape_c_keyword(field.name)
g.sb.writeln('\t${c_type}* ${fname};')
}
g.sb.writeln('} ${soa_name};')
g.sb.writeln('')
// --- new: allocate SoA container ---
g.sb.writeln('static inline ${soa_name} ${soa_name}_new(int len, int cap) {')
g.sb.writeln('\tif (cap < len) cap = len;')
g.sb.writeln('\t${soa_name} soa;')
g.sb.writeln('\tsoa.len = len;')
g.sb.writeln('\tsoa.cap = cap;')
for field in s.fields {
c_type := g.types_type_to_c(field.typ)
fname := escape_c_keyword(field.name)
if g.has_freestanding_hook_capability('alloc') {
g.sb.writeln('\tsoa.${fname} = (${c_type}*)v_platform_malloc(cap * sizeof(${c_type}));')
g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
} else if g.is_freestanding_target() {
g.sb.writeln('\tsoa.${fname} = (${c_type}*)${g.c_heap_malloc_call('cap * sizeof(${c_type})')};')
g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
} else {
g.sb.writeln('\tsoa.${fname} = (${c_type}*)calloc(cap, sizeof(${c_type}));')
}
}
g.sb.writeln('\treturn soa;')
g.sb.writeln('}')
g.sb.writeln('')
// --- get: retrieve element at index as original struct ---
g.sb.writeln('static inline ${name} ${soa_name}_get(${soa_name} soa, int i) {')
g.sb.writeln('\treturn (${name}){')
for i, field in s.fields {
comma := if i < s.fields.len - 1 { ',' } else { '' }
fname := escape_c_keyword(field.name)
g.sb.writeln('\t\t.${fname} = soa.${fname}[i]${comma}')
}
g.sb.writeln('\t};')
g.sb.writeln('}')
g.sb.writeln('')
// --- set: set element at index from original struct ---
g.sb.writeln('static inline void ${soa_name}_set(${soa_name}* soa, int i, ${name} val) {')
for field in s.fields {
fname := escape_c_keyword(field.name)
g.sb.writeln('\tsoa->${fname}[i] = val.${fname};')
}
g.sb.writeln('}')
g.sb.writeln('')
// --- push: append element, growing capacity if needed ---
g.sb.writeln('static inline void ${soa_name}_push(${soa_name}* soa, ${name} val) {')
g.sb.writeln('\tif (soa->len >= soa->cap) {')
g.sb.writeln('\t\tint new_cap = soa->cap < 8 ? 8 : soa->cap * 2;')
for field in s.fields {
c_type := g.types_type_to_c(field.typ)
fname := escape_c_keyword(field.name)
g.sb.writeln('\t\tsoa->${fname} = (${c_type}*)${g.c_heap_realloc_call('soa->${fname}',
'new_cap * sizeof(${c_type})')};')
}
g.sb.writeln('\t\tsoa->cap = new_cap;')
g.sb.writeln('\t}')
for field in s.fields {
fname := escape_c_keyword(field.name)
g.sb.writeln('\tsoa->${fname}[soa->len] = val.${fname};')
}
g.sb.writeln('\tsoa->len++;')
g.sb.writeln('}')
g.sb.writeln('')
// --- pop: remove and return last element ---
g.sb.writeln('static inline ${name} ${soa_name}_pop(${soa_name}* soa) {')
g.sb.writeln('\tif (soa->len == 0) return (${name}){0};')
g.sb.writeln('\tsoa->len--;')
g.sb.writeln('\treturn (${name}){')
for i, field in s.fields {
comma := if i < s.fields.len - 1 { ',' } else { '' }
fname := escape_c_keyword(field.name)
g.sb.writeln('\t\t.${fname} = soa->${fname}[soa->len]${comma}')
}
g.sb.writeln('\t};')
g.sb.writeln('}')
g.sb.writeln('')
// --- free: deallocate all field arrays ---
g.sb.writeln('static inline void ${soa_name}_free(${soa_name}* soa) {')
for field in s.fields {
fname := escape_c_keyword(field.name)
g.sb.writeln('\t${g.c_heap_free_call('soa->${fname}')};')
}
g.sb.writeln('\tsoa->len = 0;')
g.sb.writeln('\tsoa->cap = 0;')
g.sb.writeln('}')
g.sb.writeln('')
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,248 @@
// 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 cleanc
import v2.ast
import strings
fn (mut g Gen) gen_string_inter_literal(node ast.StringInterLiteral) {
if g.is_freestanding_target() {
g.sb.write_string(g.c_freestanding_missing_format_string_expr())
return
}
// Two-pass snprintf: first into a 256-byte stack buffer to measure,
// then use it directly or heap-allocate only when it doesn't fit.
// This avoids the unconditional heap allocation of asprintf.
// Wrapped in GCC compound expression ({ ... })
mut fmt_str := strings.new_builder(64)
for i, raw_val in node.values {
mut val := raw_val
// Strip only the outer string delimiters. Using trim_* here can over-strip
// escaped quotes in edge chunks like: 'A "${x}"', leaving a trailing `\`.
if i == 0 && val.len > 0 && val[0] in [`'`, `"`] {
val = val[1..]
}
if i == node.values.len - 1 && val.len > 0 && val[val.len - 1] in [`'`, `"`] {
val = val[..val.len - 1]
}
escaped := val.replace('%', '%%').replace('\t', '\\t')
fmt_str.write_string(escaped)
if i < node.inters.len {
inter := node.inters[i]
fmt_str.write_string(g.get_sprintf_format(inter))
}
}
fmt_lit := c_string_literal_content_to_c(fmt_str.str())
// Build the argument list string once (used in both snprintf calls).
// write_sprintf_arg writes to g.sb, so we temporarily swap it out,
// capture the result, and restore it.
mut args_sb := strings.new_builder(64)
for inter in node.inters {
args_sb.write_string(', ')
saved := g.sb
g.sb = strings.new_builder(64)
g.write_sprintf_arg(inter)
arg_str := g.sb.str()
g.sb = saved
args_sb.write_string(arg_str)
}
args_str := args_sb.str()
// Emit: try stack buffer first, fall back to malloc only if needed.
g.sb.write_string('({ char _sib[256]; int _sil = snprintf(_sib, sizeof(_sib), ${fmt_lit}${args_str}); ')
g.sb.write_string('char* _sip; if (_sil < (int)sizeof(_sib)) { _sip = memdup(_sib, _sil + 1); } ')
g.sb.write_string('else { _sip = (char*)malloc(_sil + 1); snprintf(_sip, _sil + 1, ${fmt_lit}${args_str}); } ')
g.sb.write_string('${c_v_string_expr_from_ptr_len('_sip', '_sil', false)}; })')
}
fn string_inter_needs_selector_type_fallback(expr_type string) bool {
return expr_type == '' || expr_type == 'int' || expr_type == 'int_literal'
|| expr_type == 'void' || expr_type == 'void*' || expr_type == 'voidptr'
}
fn (mut g Gen) string_inter_expr_type(expr ast.Expr) string {
expr_type := g.get_expr_type(expr)
if expr is ast.SelectorExpr && string_inter_needs_selector_type_fallback(expr_type) {
declared_type := g.selector_declared_field_type(expr)
if !string_inter_needs_selector_type_fallback(declared_type) {
return declared_type
}
field_type := g.selector_field_type(expr)
if !string_inter_needs_selector_type_fallback(field_type) {
return field_type
}
}
return expr_type
}
fn (mut g Gen) write_sprintf_arg(inter ast.StringInter) {
expr_type := g.string_inter_expr_type(inter.expr)
expr_src := g.expr_to_string(inter.expr)
fmt := g.get_sprintf_format(inter)
// Keep vararg C types aligned with the emitted format string.
// If formatter expects a non-string argument, pass expression as-is.
if !fmt.ends_with('s') {
if expr_src == '' {
g.sb.write_string('0')
} else {
g.sb.write_string(expr_src)
}
return
}
str_fn := g.get_str_fn_for_type(expr_type) or { '' }
// Float types: use V's str() for default formatting ('0.0' not '0.000000')
if expr_type in ['f64', 'f32', 'float_literal'] && inter.format == .unformatted {
str_name := if expr_type == 'f32' { 'f32__str' } else { 'f64__str' }
g.sb.write_string('${str_name}(')
if expr_src == '' {
g.sb.write_string('0')
} else {
g.sb.write_string(expr_src)
}
g.sb.write_string(').str')
return
}
if expr_type == 'string' {
if expr_src == '' {
g.sb.write_string('""')
return
}
g.sb.write_string(expr_src)
g.sb.write_string('.str')
} else if expr_type == 'bool' {
g.sb.write_string('(')
if expr_src == '' {
g.sb.write_string('false')
} else {
g.sb.write_string(expr_src)
}
g.sb.write_string(' ? "true" : "false")')
} else if str_fn != '' {
g.sb.write_string('${str_fn}(')
if expr_src == '' {
g.sb.write_string('0')
} else {
g.sb.write_string(expr_src)
}
g.sb.write_string(').str')
} else {
if expr_src == '' {
g.sb.write_string('0')
} else {
g.sb.write_string(expr_src)
}
}
}
fn (mut g Gen) get_sprintf_format(inter ast.StringInter) string {
mut fmt := '%'
mut width := inter.width
mut precision := inter.precision
// Extract width/precision from format_expr if not set explicitly
if width == 0 && precision == 0 && inter.format_expr !is ast.EmptyExpr {
if inter.format_expr is ast.BasicLiteral {
val := inter.format_expr.value
if val.contains('.') {
// ".3" or "0.3" → precision
parts := val.split('.')
if parts.len == 2 {
if parts[0].len > 0 && parts[0] != '0' {
width = parts[0].int()
}
precision = parts[1].int()
}
} else {
// Plain number → width (e.g., "03" for zero-padded)
if val.starts_with('0') && val.len > 1 {
fmt += '0'
}
width = val.int()
}
} else if inter.format_expr is ast.PrefixExpr {
// Negative alignment: -10 → left-align with width 10
if inter.format_expr.op == .minus && inter.format_expr.expr is ast.BasicLiteral {
fmt += '-'
width = inter.format_expr.expr.value.int()
}
}
}
// Width
if width > 0 {
fmt += '${width}'
}
// Precision
if precision > 0 {
fmt += '.${precision}'
}
// Format specifier
expr_type := g.string_inter_expr_type(inter.expr)
if inter.format != .unformatted {
match inter.format {
.decimal { fmt += 'd' }
.float { fmt += 'f' }
.hex { fmt += 'x' }
.octal { fmt += 'o' }
.character { fmt += 'c' }
.exponent { fmt += 'e' }
.exponent_short { fmt += 'g' }
.binary { fmt += 'd' } // binary not supported in printf, fallback to decimal
.pointer_address { fmt += 'p' }
.string { fmt += 's' }
.unformatted { fmt += 'd' }
}
return fmt
}
if inter.resolved_fmt != '' {
if expr_type in ['string', 'bool', 'f32', 'f64', 'float_literal'] {
return '%s'
}
if _ := g.get_str_fn_for_type(expr_type) {
return '%s'
}
return inter.resolved_fmt
}
// Infer from expression type
match expr_type {
'string' {
return '%s'
}
'int', 'i8', 'i16', 'i32' {
return '%d'
}
'i64' {
return '%lld'
}
'u8', 'u16', 'u32' {
return '%u'
}
'u64' {
return '%llu'
}
'f32', 'f64', 'float_literal' {
// Use %s with V's str() function for default formatting,
// since C's %f produces '0.000000' instead of V's '0.0'.
return '%s'
}
'bool' {
return '%s'
}
'rune' {
return '%c'
}
'char' {
return '%c'
}
else {
if _ := g.get_str_fn_for_type(expr_type) {
return '%s'
}
return '%d'
}
}
}
// get_str_fn_for_type returns the name of the str() function for a type, if one exists.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,7 @@
module main
fn main() {
arr := []f32{len: 4, init: -1e9}
println(int(arr[0]))
println(int(arr[3]))
}
@@ -0,0 +1,29 @@
module main
struct Foo {
a int
b int
}
fn main() {
x := Foo{
a: 1
b: 2
}
// AssocExpr lowering: Foo{...x b: 3}
y := Foo{
...x
b: 3
}
println(y.a)
println(y.b)
// &AssocExpr lowering: &Foo{...x a: 9}
p := &Foo{
...x
a: 9
}
println(p.a)
println(p.b)
}
@@ -0,0 +1,7 @@
fn main() {
a := u32(0x1234)
b := u32(0x56)
c := u32(0x78)
packed := (a >> 3) | (b << 13) | (c << 29)
println(packed)
}
@@ -0,0 +1,15 @@
struct QuaternionKey {
values [3]u16
}
fn unpack(key QuaternionKey) u32 {
packed := u32(key.values[0]) >> 3 | u32(key.values[1]) << 13 | u32(key.values[2]) << 29
return packed
}
fn main() {
key := QuaternionKey{
values: [u16(0x1234), u16(0x56), u16(0x79)]!
}
println(unpack(key))
}
@@ -0,0 +1,27 @@
module main
@[flag]
enum Perms {
read
write
execute
}
fn main() {
// .has() and .all()
mut p := Perms.read | Perms.write
println(p.has(.read))
println(p.has(.execute))
println(p.all(.read | .write))
println(p.all(.read | .execute))
// .set()
p.set(.execute)
println(p.has(.execute))
println(p.all(.read | .write | .execute))
// .clear()
p.clear(.write)
println(p.has(.write))
println(p.has(.read))
}
@@ -0,0 +1,56 @@
struct Point {
x int
}
type Primitive = []int | []string | []Point | bool | int | string | Point
fn wrap_first[T](values []T) Primitive {
if values.len > 0 {
first := values[0]
return Primitive(first)
}
return Primitive(false)
}
fn check(value Primitive) {
match value {
[]int {
int_value := wrap_first(value)
if int_value is int {
println('int:${int_value}')
} else {
println('int:bad')
}
}
[]string {
string_value := wrap_first(value)
if string_value is string {
println('string:${string_value}')
} else {
println('string:bad')
}
}
[]Point {
point_value := wrap_first(value)
if point_value is Point {
println('point:${point_value.x}')
} else {
println('point:bad')
}
}
else {}
}
}
fn main() {
ints := []int{len: 1, init: 7}
check(Primitive(ints))
strings := []string{len: 1, init: 'abc'}
check(Primitive(strings))
points := []Point{len: 1, init: Point{
x: 3
}}
check(Primitive(points))
}
@@ -0,0 +1,25 @@
module main
interface Backend {}
struct MetalBackend {}
struct NullBackend {}
fn make_backend() Backend {
return MetalBackend{}
}
fn main() {
mut backend := make_backend()
if mut backend is MetalBackend {
println('metal')
} else {
println('not-metal')
}
if mut backend is NullBackend {
println('null')
} else {
println('not-null')
}
}
@@ -0,0 +1,24 @@
module main
interface Backend {
foo()
}
struct MetalBackend {}
fn (m MetalBackend) foo() {}
fn (m MetalBackend) bar() {
println('bar')
}
fn make_backend() Backend {
return MetalBackend{}
}
fn main() {
mut backend := make_backend()
if mut backend is MetalBackend {
backend.bar()
}
}
@@ -0,0 +1,20 @@
module main
fn make_empty() map[string]int {
return {}
}
fn main() {
// Empty shorthand map literal `{}` should lower to a correct runtime map constructor.
mut m := make_empty()
key1 := 'abc'
key2 := key1.clone() // Ensure key equality is based on string content, not pointer identity.
m[key1] = 123
println(m[key2])
println(m['missing'])
// Typed empty map init should also lower to a runtime map constructor.
mut m2 := map[string]int{}
m2['x'] = 7
println(m2['x'])
}
@@ -0,0 +1,29 @@
module main
enum State {
idle
run
}
fn make_explicit() map[State]int {
return {
.idle: 10
.run: 20
}
}
fn make_from_context() map[State]int {
return {
.idle: 30
.run: 40
}
}
fn main() {
a := make_explicit()
b := make_from_context()
println(a[State.idle])
println(a[State.run])
println(b[State.idle])
println(b[State.run])
}
@@ -0,0 +1,23 @@
module main
fn mk() map[string]int {
return {
'a': 10
}
}
fn mk2() map[string]string {
return {
'x': 'y'
}
}
fn main() {
// Map index reads should lower in the transformer even when the map expression is not addressable.
println(mk()['a'])
println(mk()['missing'])
// Missing string key should return an empty string (not nil).
s := mk2()['missing']
println('>${s}<')
}
@@ -0,0 +1,102 @@
// Test runner for cleanc backend regression tests
// Run with: ./vnew run vlib/v2_toberemoved/gen/cleanc/tests/run_tests.v
// Or: cd vlib/v2_toberemoved/gen/cleanc/tests && v run run_tests.v
module main
import os
import time
fn main() {
t0 := time.now()
vroot := os.dir(@VEXE)
v2_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v')
v2_binary := os.join_path(vroot, 'cmd', 'v2', 'v2')
tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
// Build v2 compiler (with v1).
println('[*] Building v2...')
build_res := os.execute('${@VEXE} -gc none -cc cc ${v2_source} -o ${v2_binary}')
if build_res.exit_code != 0 {
eprintln('Error: Failed to build v2')
eprintln(build_res.output)
exit(1)
}
// Find all test files (*.v except run_tests.v).
test_files := os.ls(tests_dir) or {
eprintln('Error: Cannot list tests directory')
exit(1)
}
mut passed := 0
mut failed := 0
mut test_count := 0
for file in test_files {
if !file.ends_with('.v') || file == 'run_tests.v' {
continue
}
test_count++
test_path := os.join_path(tests_dir, file)
test_name := file.replace('.v', '')
println('\n[*] Testing: ${file}')
// Run v2 with cleanc backend.
v2_output := os.join_path(tests_dir, 'test_${test_name}')
v2_cmd := '${v2_binary} -gc none -backend cleanc ${test_path} -o ${v2_output}'
v2_res := os.execute(v2_cmd)
if v2_res.exit_code != 0 {
eprintln(' [FAIL] v2 compilation failed')
eprintln(v2_res.output)
failed++
continue
}
// Run the generated binary.
gen_res := os.execute(v2_output)
if gen_res.exit_code != 0 {
eprintln(' [FAIL] Generated binary exited with code ${gen_res.exit_code}')
eprintln(gen_res.output)
failed++
continue
}
// Run reference compiler (v1).
ref_res := os.execute('${@VEXE} -gc none -n -w -enable-globals run ${test_path}')
if ref_res.exit_code != 0 {
eprintln(' [FAIL] Reference compilation failed')
eprintln(ref_res.output)
failed++
continue
}
// Compare outputs.
expected := ref_res.output.trim_space().replace('\r\n', '\n')
actual := gen_res.output.trim_space().replace('\r\n', '\n')
if expected == actual {
println(' [PASS] Output matches reference')
passed++
} else {
println(' [FAIL] Output mismatch')
println(' Expected:\n${expected}')
println(' Got:\n${actual}')
failed++
}
// Clean up.
os.rm(v2_output) or {}
os.rm('${v2_output}.c') or {}
}
println('\n========================================')
println('Results: ${passed}/${test_count} tests passed')
if failed > 0 {
println('${failed} tests FAILED')
exit(1)
}
println('Total time: ${time.since(t0)}')
}
@@ -0,0 +1,13 @@
module main
@[soa]
struct Vec2 {
x f32
y f32
}
fn main() {
// Test that the SOA struct definition is generated
// The companion type Vec2_SOA should be available
println('soa_struct test: ok')
}
File diff suppressed because it is too large. Load diff