From 2d2d13d7977947ccb7071c47aaee6a4bd8d375af Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 26 Apr 2026 02:48:52 +0300 Subject: [PATCH] all: fix lots of tests --- vlib/db/mssql/config.v | 2 +- vlib/os/pipe.c.v | 6 +++ vlib/sokol/sapp/sapp_linux.c.v | 1 + vlib/sync/waitgroup.c.v | 2 +- vlib/v/checker/checker.v | 5 +- vlib/v/checker/comptime.v | 5 ++ vlib/v/eval/gen/infix_gen.v | 3 ++ vlib/v/eval/infix.v | 6 +++ vlib/v/gen/c/array.v | 5 +- vlib/v/gen/c/cgen.v | 3 ++ vlib/v/gen/c/cheaders.v | 82 ++++++++++++++++++++++++++++++++ vlib/v/gen/c/comptime.v | 2 + vlib/v/gen/c/coutput_test.v | 39 +++++++++++++-- vlib/v/gen/c/fn.v | 13 ++++- vlib/v/gen/c/utils.v | 69 +++++---------------------- vlib/v/transformer/transformer.v | 5 ++ 16 files changed, 181 insertions(+), 67 deletions(-) diff --git a/vlib/db/mssql/config.v b/vlib/db/mssql/config.v index 4b71abd30..c169fb5a0 100644 --- a/vlib/db/mssql/config.v +++ b/vlib/db/mssql/config.v @@ -46,7 +46,7 @@ fn format_odbc_value(value string) string { return '' } if needs_odbc_braces(value) { - return '{${value.replace('}', '}}')}' + return '{' + value.replace('}', '}}') + '}' } return value } diff --git a/vlib/os/pipe.c.v b/vlib/os/pipe.c.v index dadeb5ebd..fdcb53f1f 100644 --- a/vlib/os/pipe.c.v +++ b/vlib/os/pipe.c.v @@ -141,6 +141,9 @@ pub fn stdio_capture() !IOCapture { mut pipe_stdout := pipe()! mut pipe_stderr := pipe()! + flush_stdout() + flush_stderr() + // Save original file descriptors c.original_stdout_fd = fd_dup(fd_stdout) c.original_stderr_fd = fd_dup(fd_stderr) @@ -176,6 +179,9 @@ pub fn stdio_capture() !IOCapture { // stop restores the original stdout and stderr streams // This should be called to resume normal console output pub fn (mut c IOCapture) stop() { + flush_stdout() + flush_stderr() + // Restore original stdout if c.original_stdout_fd != -1 { fd_dup2(c.original_stdout_fd, fd_stdout) diff --git a/vlib/sokol/sapp/sapp_linux.c.v b/vlib/sokol/sapp/sapp_linux.c.v index a10252440..e0ea7e66b 100644 --- a/vlib/sokol/sapp/sapp_linux.c.v +++ b/vlib/sokol/sapp/sapp_linux.c.v @@ -640,6 +640,7 @@ mut: y int } +@[typedef] pub struct C.XSizeHints { mut: flags i64 diff --git a/vlib/sync/waitgroup.c.v b/vlib/sync/waitgroup.c.v index 88b741a03..1ee5a010c 100644 --- a/vlib/sync/waitgroup.c.v +++ b/vlib/sync/waitgroup.c.v @@ -84,7 +84,7 @@ pub fn (mut wg WaitGroup) wait() { if nrjobs == 0 { return } - if C.atomic_compare_exchange_weak_u64(voidptr(&wg.state), &state, state + 1) { + if C.atomic_compare_exchange_weak_u64(voidptr(&wg.state), voidptr(&state), state + 1) { wg.sem.wait() // blocks until task_count becomes 0 if C.atomic_load_u64(voidptr(&wg.state)) != 0 { panic('WaitGroup misuse: reused before previous wait() returned') diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 5079098d4..1f0f17bfb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1424,7 +1424,10 @@ fn (mut c Checker) expr_is_mutable_alias_of_immutable_source(expr ast.Expr) bool ast.Ident { if expr.obj is ast.Var && expr.obj.is_mut && c.type_has_mutable_aliasing(expr.obj.typ) { match expr.obj.expr { - ast.Ident, ast.CallExpr, ast.CastExpr, ast.AsCast, ast.ParExpr, ast.UnsafeExpr { + ast.UnsafeExpr { + return false + } + ast.Ident, ast.CallExpr, ast.CastExpr, ast.AsCast, ast.ParExpr { return c.expr_is_immutable_source(expr.obj.expr) || c.expr_is_mutable_alias_of_immutable_source(expr.obj.expr) } diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 7ef398bc2..d665cb5fe 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -2,6 +2,7 @@ // Use of this source code is governed by an MIT license that can be found in the LICENSE file. module checker +import math import os import v.ast import v.pref @@ -41,6 +42,10 @@ fn comptime_power_i64(base i64, exponent i64) i64 { return value } +fn comptime_power_f64(base f64, exponent f64) f64 { + return math.pow(base, exponent) +} + fn comptime_power_value(left ast.ComptTimeConstValue, right ast.ComptTimeConstValue) ?ast.ComptTimeConstValue { if left_i := left.i64() { if right_i := right.i64() { diff --git a/vlib/v/eval/gen/infix_gen.v b/vlib/v/eval/gen/infix_gen.v index 445939e16..7407791d8 100644 --- a/vlib/v/eval/gen/infix_gen.v +++ b/vlib/v/eval/gen/infix_gen.v @@ -127,6 +127,9 @@ fn main() { b.write_string(if lt == 'i64' { 'int' } else { 'float' }) b.write_string(" literal and \$right.type_name()')}}}") } + if op == '+' { + b.write_string("string{match right{string{return left + right}else{e.error('invalid operands to +: string and \$right.type_name()')}}}") + } b.write_string("else {e.error('invalid operands to ${op}: \$left.type_name() and \$right.type_name()')}}}") } diff --git a/vlib/v/eval/infix.v b/vlib/v/eval/infix.v index 0f4031d28..5d117d76a 100644 --- a/vlib/v/eval/infix.v +++ b/vlib/v/eval/infix.v @@ -1165,6 +1165,12 @@ fn (e &Eval) infix_expr(left Object, right Object, op token.Kind, expecting ast. } } } + string { + match right { + string { return left + right } + else { e.error('invalid operands to +: string and ${right.type_name()}') } + } + } else { e.error('invalid operands to +: ${left.type_name()} and ${right.type_name()}') } diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 374b54b2b..8d03b8a90 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -1370,7 +1370,7 @@ fn (mut g Gen) gen_array_sort_call(node ast.CallExpr, qsort_compare_fn string, i if is_array { g.write('if (') g.write_array_receiver(node.left) - g.write2('${deref_field}len > 0) { ', 'qsort(') + g.write2('${deref_field}len > 0) { ', 'v_stable_sort(') g.write_array_receiver(node.left) g.write('${deref_field}data, ') g.write_array_receiver(node.left) @@ -1380,7 +1380,7 @@ fn (mut g Gen) gen_array_sort_call(node ast.CallExpr, qsort_compare_fn string, i } else { info := g.table.final_sym(node.left_type).info as ast.ArrayFixed elem_styp := g.styp(info.elem_type) - g.write('qsort(&') + g.write('v_stable_sort(&') g.write_array_receiver(node.left) g.write(', ${info.size}, sizeof(${elem_styp}), ${qsort_compare_fn});') } @@ -2751,6 +2751,7 @@ fn (mut g Gen) refresh_array_expr_param_type(expr ast.Expr, var_name string, ele v.orig_type = ast.no_type v.smartcasts = [] v.is_unwrapped = false + g.clear_type_resolution_caches() } } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index fed6449e0..28d7858ff 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -82,6 +82,7 @@ mut: sorted_global_const_names []string file &ast.File = unsafe { nil } table &ast.Table = unsafe { nil } + mods_with_c_includes map[string]bool styp_cache map[ast.Type]string no_eq_method_types map[ast.Type]bool // types that does not need to call its auto eq methods for optimization generic_parts_cache []i8 // type idx -> 0 unknown, 1 false, 2 true @@ -380,6 +381,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO json_forward_decls: strings.new_builder(100) sql_buf: strings.new_builder(100) table: table + mods_with_c_includes: modules_with_c_includes(files) pref: pref_ fn_decl: unsafe { nil } anon_fn: unsafe { nil } @@ -1004,6 +1006,7 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) voidptr sql_buf: strings.new_builder(100) cleanup: strings.new_builder(100) table: global_g.table + mods_with_c_includes: global_g.mods_with_c_includes pref: global_g.pref fn_decl: unsafe { nil } anon_fn: unsafe { nil } diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index 6c7c737bb..f5bc2af67 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -589,6 +589,88 @@ enum { #endif #undef V_CRT_LINKAGE #undef V_CRT_CALL +static void v_stable_sort(void *base, size_t items, size_t item_size, qsort_callback_func cb) { + if (items < 2 || item_size == 0) { + return; + } + if (items > ((size_t)-1) / item_size) { + qsort(base, items, item_size, cb); + return; + } + const size_t bytes = items * item_size; + char *base_bytes = (char*)base; + char *tmp = (char*)malloc(bytes); + if (tmp == 0) { + qsort(base, items, item_size, cb); + return; + } + char *src = base_bytes; + char *dst = tmp; + for (size_t width = 1; width < items;) { + for (size_t left = 0; left < items;) { + size_t mid = left; + mid += width; + if (mid > items) { + mid = items; + } + size_t right = mid; + right += width; + if (right > items || right < mid) { + right = items; + } + size_t i = left; + size_t j = mid; + size_t k = left; + while (i < mid && j < right) { + char *leftp = src; + leftp += i * item_size; + char *rightp = src; + rightp += j * item_size; + char *dstp = dst; + dstp += k * item_size; + if (cb(leftp, rightp) <= 0) { + memcpy(dstp, leftp, item_size); + i++; + } else { + memcpy(dstp, rightp, item_size); + j++; + } + k++; + } + while (i < mid) { + char *dstp = dst; + dstp += k * item_size; + char *srcp = src; + srcp += i * item_size; + memcpy(dstp, srcp, item_size); + i++; + k++; + } + while (j < right) { + char *dstp = dst; + dstp += k * item_size; + char *srcp = src; + srcp += j * item_size; + memcpy(dstp, srcp, item_size); + j++; + k++; + } + left = right; + } + char *next_src = dst; + dst = src; + src = next_src; + if (width > items / 2) { + width = items; + } else { + width *= 2; + } + } + if (src != base_bytes) { + memcpy(base_bytes, src, bytes); + } + free(tmp); +} #if defined(__TINYC__) // https://lists.nongnu.org/archive/html/tinycc-devel/2025-10/msg00007.html // gnu headers use to #define __attribute__ to empty for non-gcc compilers diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index fcb21235a..ccd240765 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -893,6 +893,7 @@ fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type { // push_new_comptime_info saves the current comptime information fn (mut g Gen) push_new_comptime_info() { + g.clear_type_resolution_caches() g.type_resolver.info_stack << type_resolver.ResolverInfo{ saved_type_map: g.type_resolver.type_map.clone() inside_comptime_for: g.comptime.inside_comptime_for @@ -927,6 +928,7 @@ fn (mut g Gen) pop_comptime_info() { g.comptime.comptime_for_method_var = old.comptime_for_method_var g.comptime.comptime_for_method = old.comptime_for_method g.comptime.comptime_for_method_ret_type = old.comptime_for_method_ret_type + g.clear_type_resolution_caches() } fn (mut g Gen) comptime_for(node ast.ComptimeFor) { diff --git a/vlib/v/gen/c/coutput_test.v b/vlib/v/gen/c/coutput_test.v index 5c1cae9ed..8603d2849 100644 --- a/vlib/v/gen/c/coutput_test.v +++ b/vlib/v/gen/c/coutput_test.v @@ -261,6 +261,35 @@ fn test_no_main_exports_initialize_windows_runtime() { } } +fn test_c_fallback_decl_uses_module_wide_c_includes() { + os.chdir(vroot) or {} + test_source := os.join_path(os.vtmp_dir(), 'coutput_module_c_include') + os.rmdir_all(test_source) or {} + os.mkdir_all(test_source)! + defer { + os.rmdir_all(test_source) or {} + } + header_path := os.join_path(test_source, 'c_header_decl.h') + os.write_file(header_path, 'int c_header_decl(const char* input);\n')! + header_include_path := header_path.replace('\\', '/') + os.write_file(os.join_path(test_source, 'include.v'), 'module main + +#include "${header_include_path}" +')! + os.write_file(os.join_path(test_source, 'decl.v'), "module main + +fn C.c_header_decl(input &char) int + +fn main() { + C.c_header_decl(c'text') +} +")! + cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(test_source)}' + compilation := os.execute(cmd) + ensure_compilation_succeeded(compilation, cmd) + assert !compilation.output.contains('extern int c_header_decl(') +} + fn test_user_defined_windows_dllmain_disables_generated_entrypoint() { os.chdir(vroot) or {} test_source := os.join_path(os.vtmp_dir(), 'coutput_user_defined_windows_dllmain.vv') @@ -283,9 +312,9 @@ fn test_user_defined_windows_dllmain_disables_generated_entrypoint() { assert !compilation.output.contains('case DLL_PROCESS_ATTACH') } -fn test_array_sort_with_compare_uses_qsort_adapters() { +fn test_array_sort_with_compare_uses_stable_sort_adapters() { os.chdir(vroot) or {} - test_source := os.join_path(os.vtmp_dir(), 'coutput_array_sort_with_compare_qsort_adapter.vv') + test_source := os.join_path(os.vtmp_dir(), 'coutput_array_sort_with_compare_stable_sort.vv') source_lines := [ 'module main', '', @@ -318,10 +347,10 @@ fn test_array_sort_with_compare_uses_qsort_adapters() { normalized = normalized.replace(' ', ' ') } assert normalized.contains('int main__by_x_qsort_adapter(const void* a, const void* b) { return main__by_x((main__Foo*)a, (main__Foo*)b); }') - assert normalized.contains('if (xs.len > 0) { qsort(xs.data, xs.len, xs.element_size, main__by_x_qsort_adapter); }') - assert normalized.contains('qsort(&ys, 2, sizeof(main__Foo), main__by_x_qsort_adapter);') + assert normalized.contains('if (xs.len > 0) { v_stable_sort(xs.data, xs.len, xs.element_size, main__by_x_qsort_adapter); }') + assert normalized.contains('v_stable_sort(&ys, 2, sizeof(main__Foo), main__by_x_qsort_adapter);') assert normalized.contains('_qsort_adapter(const void* a, const void* b) { return compare_') - assert normalized.contains('qsort(zs.data, zs.len, zs.element_size, compare_') + assert normalized.contains('v_stable_sort(zs.data, zs.len, zs.element_size, compare_') assert normalized.contains('_qsort_adapter);') } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index fa0303484..fa7d19de7 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -774,6 +774,16 @@ fn file_has_c_includes(file &ast.File) bool { return false } +fn modules_with_c_includes(files []&ast.File) map[string]bool { + mut mods := map[string]bool{} + for file in files { + if file_has_c_includes(file) { + mods[file.mod.name] = true + } + } + return mods +} + fn file_imports_c_header_module(file &ast.File) bool { if file == unsafe { nil } { return false @@ -809,7 +819,8 @@ fn (g &Gen) c_prelude_provides_decl(c_sym_name string) bool { fn (g &Gen) should_emit_c_fallback_decl(node ast.FnDecl) bool { c_sym_name := node.name.all_after_first('C__').all_after_first('C.') if node.language != .c || node.is_c_extern || file_has_c_includes(node.source_file) - || g.module_has_c_header_module(node.source_file) || g.c_prelude_provides_decl(c_sym_name) { + || node.mod in g.mods_with_c_includes || g.module_has_c_header_module(node.source_file) + || g.c_prelude_provides_decl(c_sym_name) { return false } if node.source_file == unsafe { nil } { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index fcc83aa8b..ab5629295 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -26,6 +26,19 @@ fn (g &Gen) type_resolution_context_key() u64 { key = cgen_resolution_hash_mix(key, 1) } key = cgen_resolution_hash_mix(key, u64(g.cur_struct_init_typ)) + key = cgen_resolution_hash_mix(key, u64(g.cur_concrete_types.len)) + for concrete_type in g.cur_concrete_types { + key = cgen_resolution_hash_mix(key, u64(concrete_type)) + } + key = cgen_resolution_hash_mix(key, u64(g.active_call_concrete_types.len)) + for concrete_type in g.active_call_concrete_types { + key = cgen_resolution_hash_mix(key, u64(concrete_type)) + } + if g.comptime != unsafe { nil } { + key = cgen_resolution_hash_mix(key, u64(g.comptime.comptime_loop_id)) + key = cgen_resolution_hash_mix(key, u64(g.comptime.comptime_for_field_type)) + key = cgen_resolution_hash_mix(key, u64(g.comptime.comptime_for_method_ret_type)) + } return key } @@ -941,58 +954,6 @@ fn (mut g Gen) resolved_or_block_value_type(or_expr ast.OrExpr) ast.Type { return 0 } -fn (mut g Gen) direct_concrete_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type { - if g.has_current_generic_context() || g.has_active_call_generic_context() - || g.inside_struct_init { - return 0 - } - match expr { - ast.Ident { - if expr.or_expr.kind != .absent { - return 0 - } - if expr.obj is ast.Var { - if expr.obj.is_unwrapped || expr.obj.orig_type != 0 || expr.obj.smartcasts.len > 0 - || expr.obj.ct_type_var != .no_comptime || expr.obj.is_auto_deref { - return 0 - } - } - } - ast.SelectorExpr { - if expr.or_block.kind != .absent { - return 0 - } - } - ast.IndexExpr { - if expr.or_expr.kind != .absent { - return 0 - } - } - ast.InfixExpr {} - ast.PostfixExpr { - if expr.op == .question { - return 0 - } - } - ast.ArrayInit, ast.AsCast, ast.BoolLiteral, ast.CastExpr, ast.CharLiteral, ast.EnumVal, - ast.FloatLiteral, ast.IntegerLiteral, ast.MapInit, ast.Nil, ast.None, ast.StringLiteral, - ast.StructInit {} - else { - return 0 - } - } - - mut direct_typ := expr.type() - if direct_typ == 0 || direct_typ == ast.void_type { - direct_typ = default_typ - } - if direct_typ == 0 || direct_typ == ast.void_type || direct_typ.has_flag(.generic) - || g.type_has_unresolved_generic_parts(direct_typ) { - return 0 - } - return direct_typ -} - // resolve_selector_smartcast_type resolves the final smartcast type for a // selector expression in generic contexts. When a field like `val.field` has // nested smartcasts (e.g., option unwrap then sumtype variant), the scope @@ -1013,10 +974,6 @@ fn (mut g Gen) resolve_selector_smartcast_type(node ast.SelectorExpr) ast.Type { } fn (mut g Gen) resolved_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type { - direct_typ := g.direct_concrete_expr_type(expr, default_typ) - if direct_typ != 0 { - return direct_typ - } match expr { ast.ParExpr { return g.resolved_expr_type(expr.expr, default_typ) diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index 901134ce2..72c833e87 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -3,6 +3,7 @@ // that can be found in the LICENSE file. module transformer +import math import v.pref import v.ast import v.token @@ -121,6 +122,10 @@ fn folded_power_i64(base i64, exponent i64) i64 { return value } +fn folded_power_f64(base f64, exponent f64) f64 { + return math.pow(base, exponent) +} + pub fn (mut t Transformer) find_new_range(node ast.AssignStmt) { if !t.pref.is_prod { return -- 2.39.5