From 3c0c27933ec8efde92625344dbfb286f4e7407bc Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 19 Apr 2026 01:07:30 +0300 Subject: [PATCH] all: fix more tests --- vlib/builtin/js/builtin.js.v | 6 +- vlib/builtin/js/map.js.v | 27 +++++- vlib/db/pg/orm.v | 12 +-- vlib/net/http/server_test.v | 42 +++++---- vlib/v/builder/cc.v | 25 +++--- vlib/v/builder/cc_test.v | 15 ++++ vlib/v/gen/c/fn.v | 32 ++++++- vlib/v/gen/js/auto_str_methods.v | 8 +- vlib/v/gen/js/infix.v | 5 ++ vlib/v/gen/js/js.v | 145 +++++++++++++++++++++---------- vlib/v/gen/js/map.v | 6 ++ 11 files changed, 236 insertions(+), 87 deletions(-) diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index 093858386..7c24a1913 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -105,7 +105,11 @@ fn js_stacktrace() string { #return new map(cloned); #} #if (typeof value !== 'object') return value; -#if (typeof value.$toJS === 'function') return value; +#if (typeof value.$toJS === 'function') { +#let cloned = Object.create(Object.getPrototypeOf(value) || Object.prototype); +#for (const key of Object.keys(value)) cloned[key] = v_clone_value(value[key]); +#return cloned; +#} #let cloned; #try { #cloned = typeof value.constructor === 'function' ? new value.constructor({}) : Object.create(Object.getPrototypeOf(value)); diff --git a/vlib/builtin/js/map.js.v b/vlib/builtin/js/map.js.v index eec4815f4..3e07ea70e 100644 --- a/vlib/builtin/js/map.js.v +++ b/vlib/builtin/js/map.js.v @@ -8,9 +8,14 @@ pub: fn (mut m map) internal_set(key JS.Any, val JS.Any) { //$if es5 { + #let skey = key; #if (key.hasOwnProperty('$toJS')) key = key.$toJS(); - #if (!(key in m.val.map)) m.val.length++; + #if (!(key in m.val.map)) { + #m.val.length++; + #m.val.map[key] = { key: skey, val: val }; + #} else { #m.val.map[key].val = val + #} /*} $else { # if (key.hasOwnProperty('$toJS')) key = key.$toJS(); # m.val.m.set(key,val); @@ -23,7 +28,11 @@ fn (mut m map) internal_get(key JS.Any) JS.Any { mut val := JS.Any(unsafe { nil }) //$if es5 { #if (typeof key != "string" && key.hasOwnProperty('$toJS')) key = key.$toJS(); - #val = m.val.map[key].val + #if (key in m.val.map) { + #val = m.val.map[key].val + #} else { + #val = js_undefined() + #} /*} $else { # if (key.hasOwnProperty('$toJS')) key = key.$toJS(); # val = m.val.m.get(key) @@ -40,11 +49,21 @@ fn (mut m map) internal_get(key JS.Any) JS.Any { pub fn (mut m map) delete(key JS.Any) { #let k = key.hasOwnProperty('$toJS') ? key.$toJS() : key; - #if (delete m.val.map[k]) { m.val.length--; }; + #if (k in m.val.map) { + #delete m.val.map[k]; + #m.val.length--; + #} _ := key } +pub fn (m map) clone() map { + mut res := m + #res = v_clone_value(m) + + return res +} + pub fn (m &map) free() {} pub fn (m map) keys() array { @@ -72,4 +91,4 @@ pub fn (m map) values() array { #return res; #} -#map.prototype.getOrSet = function (key, init) { if (this.map.has(key)) { return this.map.get(key); } else { this.map.set(key,init); return init; } } +#map.prototype.getOrSet = function (key, init) { const skey = key; if (typeof key != "string" && key.hasOwnProperty('$toJS')) { key = key.$toJS() } if (key in this.map) { return this.map[key].val; } this.length++; this.map[key] = { key: skey, val: init }; return this.map[key].val; } diff --git a/vlib/db/pg/orm.v b/vlib/db/pg/orm.v index da3730bac..61f127b7b 100644 --- a/vlib/db/pg/orm.v +++ b/vlib/db/pg/orm.v @@ -80,32 +80,32 @@ pub fn (db DB) drop(table orm.Table) ! { } // orm_begin starts a transaction for ORM helpers. -pub fn (mut db DB) orm_begin() ! { +pub fn (db DB) orm_begin() ! { db.begin()! } // orm_commit commits a transaction for ORM helpers. -pub fn (mut db DB) orm_commit() ! { +pub fn (db DB) orm_commit() ! { db.commit()! } // orm_rollback rolls back a transaction for ORM helpers. -pub fn (mut db DB) orm_rollback() ! { +pub fn (db DB) orm_rollback() ! { db.rollback()! } // orm_savepoint creates a savepoint for ORM helpers. -pub fn (mut db DB) orm_savepoint(name string) ! { +pub fn (db DB) orm_savepoint(name string) ! { db.savepoint(name)! } // orm_rollback_to rolls back to a savepoint for ORM helpers. -pub fn (mut db DB) orm_rollback_to(name string) ! { +pub fn (db DB) orm_rollback_to(name string) ! { db.rollback_to(name)! } // orm_release_savepoint releases a savepoint for ORM helpers. -pub fn (mut db DB) orm_release_savepoint(name string) ! { +pub fn (db DB) orm_release_savepoint(name string) ! { db.release_savepoint(name)! } diff --git a/vlib/net/http/server_test.v b/vlib/net/http/server_test.v index f2e2de4d4..e59ecbc3c 100644 --- a/vlib/net/http/server_test.v +++ b/vlib/net/http/server_test.v @@ -119,10 +119,9 @@ fn test_server_custom_handler() { defer { log.warn('${@FN} finished') } - mut handler := MyHttpHandler{} mut server := &http.Server{ accept_timeout: atimeout - handler: handler + handler: MyHttpHandler{} addr: '127.0.0.1:18197' } t := spawn server.listen_and_serve() @@ -179,10 +178,14 @@ fn test_server_custom_handler() { server.stop() t.wait() - assert handler.counter == 5 - assert handler.oks == 3 - assert handler.not_founds == 1 - assert handler.redirects == 1 + if mut server.handler is MyHttpHandler { + assert server.handler.counter == 5 + assert server.handler.oks == 3 + assert server.handler.not_founds == 1 + assert server.handler.redirects == 1 + } else { + assert false, 'expected MyHttpHandler, got ${typeof(server.handler).name}' + } } struct ProgressCalls { @@ -406,10 +409,9 @@ fn (mut handler KeepAliveHandler) handle(req http.Request) http.Response { fn test_server_keep_alive() { log.warn('${@FN} started') defer { log.warn('${@FN} finished') } - mut handler := KeepAliveHandler{} mut server := &http.Server{ accept_timeout: atimeout - handler: handler + handler: KeepAliveHandler{} addr: '127.0.0.1:18198' show_startup_message: false } @@ -457,17 +459,20 @@ fn test_server_keep_alive() { t.wait() // Verify all 3 requests were handled - assert handler.request_count == 3 + if mut server.handler is KeepAliveHandler { + assert server.handler.request_count == 3 + } else { + assert false, 'expected KeepAliveHandler, got ${typeof(server.handler).name}' + } } fn test_server_keep_alive_many_requests() { log.warn('${@FN} started') defer { log.warn('${@FN} finished') } total_requests := 64 - mut handler := KeepAliveHandler{} mut server := &http.Server{ accept_timeout: atimeout - handler: handler + handler: KeepAliveHandler{} addr: '127.0.0.1:18199' show_startup_message: false max_keep_alive_requests: 0 @@ -506,16 +511,19 @@ fn test_server_keep_alive_many_requests() { server.stop() t.wait() - assert handler.request_count == total_requests + 1 + if mut server.handler is KeepAliveHandler { + assert server.handler.request_count == total_requests + 1 + } else { + assert false, 'expected KeepAliveHandler, got ${typeof(server.handler).name}' + } } fn test_server_max_keep_alive_requests() { log.warn('${@FN} started') defer { log.warn('${@FN} finished') } - mut handler := KeepAliveHandler{} mut server := &http.Server{ accept_timeout: atimeout - handler: handler + handler: KeepAliveHandler{} addr: '127.0.0.1:18200' show_startup_message: false max_keep_alive_requests: 3 // Limit to 3 requests per connection @@ -563,7 +571,11 @@ fn test_server_max_keep_alive_requests() { server.stop() t.wait() - assert handler.request_count == 3 + if mut server.handler is KeepAliveHandler { + assert server.handler.request_count == 3 + } else { + assert false, 'expected KeepAliveHandler, got ${typeof(server.handler).name}' + } } fn read_http_response(mut conn net.TcpConn) !string { diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index f88961b7c..81d89277e 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -967,6 +967,18 @@ fn (v &Builder) rsp_safe_arg(arg string) string { return arg } +fn (v &Builder) should_use_rsp(rsp_args []string) bool { + if v.pref.no_rsp || v.pref.os == .termux { + return false + } + for arg in rsp_args { + if arg.contains("'\\''") || arg.contains('\n') || arg.contains('\r') { + return false + } + } + return true +} + fn (v &Builder) c_project_source_name() string { mut output_name := os.file_name(v.pref.out_name) if output_name == '' { @@ -1206,21 +1218,12 @@ pub fn (mut v Builder) cc() { v.dump_c_options(all_args) rsp_args := all_args.map(v.rsp_safe_arg(it)) shell_args := rsp_args.join(' ') - mut str_args := if v.pref.no_rsp { + mut should_use_rsp := v.should_use_rsp(rsp_args) + mut str_args := if !should_use_rsp { shell_args.replace('\n', ' ') } else { shell_args } - mut should_use_rsp := !v.pref.no_rsp - if should_use_rsp { - for arg in rsp_args { - if arg.contains("'\\''") || arg.contains('\n') || arg.contains('\r') { - should_use_rsp = false - str_args = shell_args - break - } - } - } mut cmd := '${v.quote_compiler_name(ccompiler)} ${str_args}' if v.pref.parallel_cc { // In parallel cc mode, all we want in cc() is build the str_args. diff --git a/vlib/v/builder/cc_test.v b/vlib/v/builder/cc_test.v index b1eb216ca..c12151e5d 100644 --- a/vlib/v/builder/cc_test.v +++ b/vlib/v/builder/cc_test.v @@ -170,6 +170,21 @@ fn test_live_termux_linker_args_include_rdynamic_without_debug() { assert linker_args.contains('-rdynamic') } +fn test_should_use_rsp_for_linux_by_default() { + builder := new_test_builder([hello_world_example()]) + assert builder.should_use_rsp(['-o', builder.out_name_c]) +} + +fn test_should_not_use_rsp_for_termux() { + builder := new_test_builder(['-os', 'termux', hello_world_example()]) + assert !builder.should_use_rsp(['-o', builder.out_name_c]) +} + +fn test_should_not_use_rsp_for_args_with_embedded_single_quotes() { + builder := new_test_builder([hello_world_example()]) + assert !builder.should_use_rsp(["'\\''"]) +} + fn test_setup_ccompiler_options_detects_cc_alias_path_as_clang() { $if windows { return diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ce573f216..a50c7ced9 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3,6 +3,7 @@ // that can be found in the LICENSE file. module c +import os import strings import v.ast import v.util @@ -677,8 +678,37 @@ fn file_has_c_includes(file &ast.File) bool { return false } +fn file_imports_c_header_module(file &ast.File) bool { + if file == unsafe { nil } { + return false + } + for imp in file.imports { + if imp.source_name.ends_with('.c') { + return true + } + } + return false +} + +fn (g &Gen) module_has_c_header_module(file &ast.File) bool { + if file_imports_c_header_module(file) { + return true + } + if file == unsafe { nil } || g.table == unsafe { nil } || file.path == '' { + return false + } + helper_dir := os.join_path(os.dir(file.path), 'c') + for path in g.table.filelist { + if path.starts_with(helper_dir + os.path_separator) { + return true + } + } + return false +} + fn (g &Gen) should_emit_c_fallback_decl(node ast.FnDecl) bool { - if node.language != .c || node.is_c_extern || file_has_c_includes(node.source_file) { + if node.language != .c || node.is_c_extern || file_has_c_includes(node.source_file) + || g.module_has_c_header_module(node.source_file) { return false } if node.source_file == unsafe { nil } { diff --git a/vlib/v/gen/js/auto_str_methods.v b/vlib/v/gen/js/auto_str_methods.v index 6c84b835b..83167dfc7 100644 --- a/vlib/v/gen/js/auto_str_methods.v +++ b/vlib/v/gen/js/auto_str_methods.v @@ -634,7 +634,11 @@ fn (mut g JsGen) gen_str_for_map(info ast.Map, _styp string, str_fn_name string) g.definitions.writeln('\tfor (let j = 0; j < keys.length;j++) {') g.definitions.writeln('\t\tlet key = keys[j];') g.definitions.writeln('\t\tlet value = m.map[key].val;') - g.definitions.writeln('\t\tkey = new ${key_styp}(key);') + if key_sym.kind == .enum { + g.definitions.writeln('\t\tkey = +key;') + } else { + g.definitions.writeln('\t\tkey = new ${key_styp}(key);') + } if key_sym.kind == .string { g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string("\'" + key.str + "\'"));') } else if key_sym.kind == .rune { @@ -653,7 +657,7 @@ fn (mut g JsGen) gen_str_for_map(info ast.Map, _styp string, str_fn_name string) } else if should_use_indent_func(val_sym.kind) && !val_sym.has_method('str') { g.definitions.writeln('\t\tstrings__Builder_write_string(sb, indent_${elem_str_fn_name}(value, indent_count));') } else if val_sym.kind in [.f32, .f64] { - g.definitions.writeln('\t\tstrings__Builder_write_string(sb, value.val + "");') + g.definitions.writeln('\t\tstrings__Builder_write_string(sb, new string(value.val + ""));') } else if val_sym.kind == .rune { g.definitions.writeln('\t\tlet x = new string("\`" + String.fromCharCode(value.val) + "\`");') g.definitions.writeln('\t\tstrings__Builder_write_string(sb,x);') diff --git a/vlib/v/gen/js/infix.v b/vlib/v/gen/js/infix.v index 96d0d5417..93da32d88 100644 --- a/vlib/v/gen/js/infix.v +++ b/vlib/v/gen/js/infix.v @@ -334,6 +334,11 @@ fn (mut g JsGen) infix_expr_left_shift_op(node ast.InfixExpr) { // arr << val array_info := left.unaliased_sym.info as ast.Array g.write('array_push(') + old_inside_left_shift := g.inside_left_shift + g.inside_left_shift = true + defer { + g.inside_left_shift = old_inside_left_shift + } //&& array_info.elem_type != g.unwrap_generic(node.right_type) if right.unaliased_sym.kind == .array && array_info.elem_type != right.typ { g.expr(node.left) diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 09d0fff0d..f4566ac00 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1403,28 +1403,35 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { mut array_set := false mut map_set := false if left is ast.IndexExpr { - g.expr(left.left) - if left.left_type.is_ptr() { - g.write('.valueOf()') - } array_set = true if left.is_map { map_set = true - // FIXME: if you update a key already in the map, it will still - // increment length, which it shouldn't since length should match - // the actual amount of elements in the map - // using a getter that calls JS Object.entries().length or - // Object.values().length to get the real length would probably - // work, but I'm hesitant about the amount of overhead this will - // introduce since those functions return arrays and not iterables, - // which might consume a lot of memory and cycles to set up + g.write('if (!') + g.expr(left.left) + if left.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.has(') + g.expr(left.index) + g.write('.\$toJS())) ') + g.expr(left.left) + if left.left_type.is_ptr() { + g.write('.valueOf()') + } g.writeln('.length++;') g.expr(left.left) + if left.left_type.is_ptr() { + g.write('.valueOf()') + } g.write('.map[') g.expr(left.index) g.write('.\$toJS()] = { val: ') } else { + g.expr(left.left) + if left.left_type.is_ptr() { + g.write('.valueOf()') + } g.write('.arr.set(') g.write('new int(') g.cast_stack << ast.int_type_idx @@ -3113,17 +3120,15 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { } g.write(')') } else if left_sym.kind == .map { - g.expr(expr.left) - if expr.is_setter && !g.inside_left_shift { + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } g.inside_map_set = true g.write('.getOrSet(') - } else { - g.write('.get(') - } - g.expr(expr.index) - g.write('.\$toJS()') - if expr.is_setter { + g.expr(expr.index) + g.write('.\$toJS()') // g.write(', ${g.to_js_typ_val(left_typ.)') match left_sym.info { ast.Map { @@ -3133,8 +3138,28 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { verror('unreachable') } } + + g.write(')') + } else { + match left_sym.info { + ast.Map { + tmp := g.new_tmp_var() + g.write('(function() { let ${tmp} = ') + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.get(') + g.expr(expr.index) + g.write('.\$toJS()); return js_is_undefined(${tmp}).valueOf() ? ') + g.write(g.to_js_typ_val(left_sym.info.value_type)) + g.write(' : ${tmp}; })()') + } + else { + verror('unreachable') + } + } } - g.write(')') } else if left_sym.kind == .string { if expr.is_setter { // TODO: What's the best way to do this? @@ -4064,36 +4089,48 @@ fn (mut g JsGen) gen_postfix_index_expr(expr ast.IndexExpr, op token.Kind) { } g.write(')') } else if left_sym_kind == .map { - g.expr(expr.left) + lsym := g.table.sym(expr.left_type) + value_typ := match lsym.info { + ast.Map { + lsym.info.value_type + } + else { + verror('unreachable') + ast.void_type + } + } if expr.is_setter { - g.inside_map_set = true - g.write('.map.set(') - } else { - g.write('.map.get(') - } - g.expr(expr.index) - g.write('.\$toJS()') - if !expr.is_setter { - g.write(')') - } else { - g.write(',') - lsym := g.table.sym(expr.left_type) - key_typ := match lsym.info { - ast.Map { - lsym.info.value_type - } - else { - verror('unreachable') - } + g.write('if (!') + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') } - - g.write('new ${g.styp(key_typ)}(') - + g.write('.has(') + g.expr(expr.index) + g.write('.\$toJS())) ') + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.writeln('.length++;') g.expr(expr.left) - g.write('.map.get(') + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.map[') g.expr(expr.index) - g.write('.\$toJS())') + g.write('.\$toJS()] = { val: ') + g.write('new ${g.styp(value_typ)}(') + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.getOrSet(') + g.expr(expr.index) + g.write('.\$toJS(), ') + g.write(g.to_js_typ_val(value_typ)) + g.write(')') match op { .inc { g.write('.val + 1)') @@ -4106,7 +4143,21 @@ fn (mut g JsGen) gen_postfix_index_expr(expr ast.IndexExpr, op token.Kind) { } } - g.write(')') + g.write(', key: ') + g.expr(expr.index) + g.write(' }') + } else { + tmp := g.new_tmp_var() + g.write('(function() { let ${tmp} = ') + g.expr(expr.left) + if expr.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write('.get(') + g.expr(expr.index) + g.write('.\$toJS()); return js_is_undefined(${tmp}).valueOf() ? ') + g.write(g.to_js_typ_val(value_typ)) + g.write(' : ${tmp}; })()') } } else if left_sym_kind == .string { if expr.is_setter { diff --git a/vlib/v/gen/js/map.v b/vlib/v/gen/js/map.v index 7450b8435..a75caa9fe 100644 --- a/vlib/v/gen/js/map.v +++ b/vlib/v/gen/js/map.v @@ -3,6 +3,8 @@ module js import v.ast const special_map_methods = [ + 'clone', + 'delete', 'keys', 'values', ] @@ -11,5 +13,9 @@ fn (mut g JsGen) gen_map_method_call(node ast.CallExpr) { g.write('map_${node.name}(') g.expr(node.left) g.gen_deref_ptr(node.left_type) + for arg in node.args { + g.write(', ') + g.expr(arg.expr) + } g.write(')') } -- 2.39.5