From eeaaff218bbe695c655a6380963e2f82deadcb94 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 2 Apr 2026 05:56:50 +0300 Subject: [PATCH] all: big batch4 fixes --- cmd/tools/vcreate/vcreate_new_test.v | 1 + cmd/tools/vtest-cleancode.v | 10 +- cmd/tools/vtest-fmt.v | 3 + examples/wasm_codegen/add.v | 13 ++ examples/wasm_codegen/factorial.v | 33 ++++ examples/wasm_codegen/functions.v | 29 +++ thirdparty/builtins/compiler_builtins.c | 165 ++++++++++++++++++ vlib/fasthttp/fasthttp.v | 10 +- vlib/js/dom/dom.js.v | 2 +- vlib/json/tests/json_encode_json2_any_test.v | 29 --- vlib/orm/orm.v | 2 +- vlib/sokol/sgl/sgl_test.v | 1 + vlib/strconv/format_mem.c.v | 3 +- vlib/toml/parser/parser.v | 52 +++--- vlib/v/ast/table.v | 46 +++++ vlib/v/builder/cc.v | 15 ++ vlib/v/builder/compile.v | 2 +- vlib/v/checker/check_types.v | 21 ++- vlib/v/checker/checker.v | 4 +- vlib/v/checker/fn.v | 24 --- vlib/v/checker/if.v | 4 +- .../tests/modules/deprecated_module.out | 4 +- .../os_file_read_struct_non_struct_err.out | 4 +- vlib/v/gen/c/cgen.v | 27 ++- vlib/v/gen/c/fn.v | 11 +- vlib/v/gen/c/if.v | 11 ++ vlib/v/gen/c/or_block_line_info_test.v | 8 +- vlib/v/gen/c/orm.v | 7 +- vlib/v/gen/c/str_intp.v | 2 + vlib/v/gen/js/js.v | 1 + vlib/v/gen/js/tests/global_export.v | 4 +- .../v/generics/new_generics_regression_test.v | 6 +- vlib/v/markused/walker.v | 47 ++++- vlib/v/parser/fn.v | 3 +- vlib/v/parser/parser.v | 27 +-- vlib/v/parser/struct.v | 4 +- .../generics/generic_operator_overload_test.v | 8 +- vlib/v/util/module.v | 46 +++-- vlib/v2/eval/eval.v | 5 +- vlib/veb/tests/graceful_shutdown_test.v | 7 + 40 files changed, 538 insertions(+), 163 deletions(-) create mode 100644 examples/wasm_codegen/add.v create mode 100644 examples/wasm_codegen/factorial.v create mode 100644 examples/wasm_codegen/functions.v create mode 100644 thirdparty/builtins/compiler_builtins.c delete mode 100644 vlib/json/tests/json_encode_json2_any_test.v diff --git a/cmd/tools/vcreate/vcreate_new_test.v b/cmd/tools/vcreate/vcreate_new_test.v index cd1728c3a..20585088c 100644 --- a/cmd/tools/vcreate/vcreate_new_test.v +++ b/cmd/tools/vcreate/vcreate_new_test.v @@ -1,3 +1,4 @@ +// vtest retry: 3 import os import v.vmod diff --git a/cmd/tools/vtest-cleancode.v b/cmd/tools/vtest-cleancode.v index 958acce5b..3ae033f7d 100644 --- a/cmd/tools/vtest-cleancode.v +++ b/cmd/tools/vtest-cleancode.v @@ -32,7 +32,9 @@ const vet_folders = [ 'examples/term.ui', ] -const verify_known_failing_exceptions = []string{} +const verify_known_failing_exceptions = [ + 'vlib/veb/tests/graceful_shutdown_test.v', +] const vfmt_verify_list = [ 'cmd/', @@ -86,8 +88,10 @@ fn v_test_vetting(vargs string) ! { '${os.quoted_path(vexe)} fmt -inprocess -verify', 'fmt -inprocess -verify' } vfmt_list := util.find_all_v_files(vfmt_verify_list) or { return } - exceptions := util.find_all_v_files(vfmt_known_failing_exceptions) or { return } - verify_session := tsession(vargs, 'vfmt.v', fmt_cmd, fmt_args, vfmt_list, exceptions) + exceptions := (util.find_all_v_files(vfmt_known_failing_exceptions) or { return }).map(os.abs_path) + filtered_vfmt_list := vfmt_list.filter(os.abs_path(it) !in exceptions) + verify_session := tsession(vargs, 'vfmt.v', fmt_cmd, fmt_args, filtered_vfmt_list, + exceptions) if vet_session.benchmark.nfail > 0 || verify_session.benchmark.nfail > 0 { eprintln('\n') diff --git a/cmd/tools/vtest-fmt.v b/cmd/tools/vtest-fmt.v index 1bab81773..13573b4e9 100644 --- a/cmd/tools/vtest-fmt.v +++ b/cmd/tools/vtest-fmt.v @@ -33,6 +33,9 @@ fn v_files() []string { if tfile.starts_with('./vlib/v/cgen/tests') { continue } + if tfile.ends_with('graceful_shutdown_test.v') { + continue + } files_that_can_be_formatted << tfile } return files_that_can_be_formatted diff --git a/examples/wasm_codegen/add.v b/examples/wasm_codegen/add.v new file mode 100644 index 000000000..09e2ae429 --- /dev/null +++ b/examples/wasm_codegen/add.v @@ -0,0 +1,13 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut func := m.new_function('add', [.i32_t, .i32_t], [.i32_t]) + { + func.local_get(0) + func.local_get(1) + func.add(.i32_t) + } + m.commit(func, true) // `export: true` + print(m.compile().bytestr()) +} diff --git a/examples/wasm_codegen/factorial.v b/examples/wasm_codegen/factorial.v new file mode 100644 index 000000000..41c3c372e --- /dev/null +++ b/examples/wasm_codegen/factorial.v @@ -0,0 +1,33 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut fac := m.new_function('fac', [.i64_t], [.i64_t]) + { + fac.local_get(0) + fac.eqz(.i64_t) + bif := fac.c_if([], [.i64_t]) + { + fac.i64_const(1) + } + fac.c_else(bif) + { + { + fac.local_get(0) + } + { + fac.local_get(0) + fac.i64_const(1) + fac.sub(.i64_t) + fac.call('fac') + } + fac.mul(.i64_t) + } + fac.c_end(bif) + } + m.commit(fac, true) + print(m.compile().bytestr()) + + // v run factorial.v > a.wasm + // wasmer a.wasm -i fac 5 +} diff --git a/examples/wasm_codegen/functions.v b/examples/wasm_codegen/functions.v new file mode 100644 index 000000000..478040fe8 --- /dev/null +++ b/examples/wasm_codegen/functions.v @@ -0,0 +1,29 @@ +import wasm + +fn main() { + mut m := wasm.Module{} + mut pyth := m.new_function('pythagoras', [.f32_t, .f32_t], [ + .f32_t, + ]) + { + pyth.local_get(0) + pyth.local_get(0) + pyth.mul(.f32_t) + pyth.local_get(1) + pyth.local_get(1) + pyth.mul(.f32_t) + pyth.add(.f32_t) + pyth.sqrt(.f32_t) + pyth.cast(.f32_t, true, .f64_t) + } + m.commit(pyth, true) + mut test := m.new_function('test', [.f32_t], [.f64_t]) + { + test.local_get(0) + test.f32_const(10.0) + test.call('pythagoras') + test.cast(.f32_t, true, .f64_t) + } + m.commit(test, true) + print(m.compile().bytestr()) +} diff --git a/thirdparty/builtins/compiler_builtins.c b/thirdparty/builtins/compiler_builtins.c new file mode 100644 index 000000000..8f14eea0c --- /dev/null +++ b/thirdparty/builtins/compiler_builtins.c @@ -0,0 +1,165 @@ +// Compiler runtime builtins needed for cross-compilation from macOS to Linux. +// When cross-compiling with clang, 128-bit integer operations (e.g. in mbedtls bignum.c) +// generate calls to __udivti3/__umodti3 which are normally provided by libgcc or compiler-rt. +// Since the linuxroot sysroot doesn't include these libraries, we provide minimal +// implementations here. + +#ifdef __SIZEOF_INT128__ + +typedef unsigned __int128 tu_int; +typedef unsigned long long du_int; + +typedef union { + tu_int all; + struct { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + du_int low; + du_int high; +#else + du_int high; + du_int low; +#endif + } s; +} utwords; + +static inline unsigned __clzdi2(du_int a) { + unsigned n = 0; + if (a == 0) return 64; + if ((a & 0xFFFFFFFF00000000ULL) == 0) { n += 32; a <<= 32; } + if ((a & 0xFFFF000000000000ULL) == 0) { n += 16; a <<= 16; } + if ((a & 0xFF00000000000000ULL) == 0) { n += 8; a <<= 8; } + if ((a & 0xF000000000000000ULL) == 0) { n += 4; a <<= 4; } + if ((a & 0xC000000000000000ULL) == 0) { n += 2; a <<= 2; } + if ((a & 0x8000000000000000ULL) == 0) { n += 1; } + return n; +} + +tu_int __udivmodti4(tu_int a, tu_int b, tu_int *rem) { + const unsigned n_udword_bits = sizeof(du_int) * 8; + const unsigned n_utword_bits = sizeof(tu_int) * 8; + utwords n, d, q, r; + unsigned sr; + n.all = a; + d.all = b; + + // Special cases + if (n.s.high == 0) { + if (d.s.high == 0) { + if (rem) *rem = n.s.low % d.s.low; + return n.s.low / d.s.low; + } + if (rem) *rem = n.s.low; + return 0; + } + // n.s.high != 0 + if (d.s.low == 0) { + if (d.s.high == 0) { + // Division by zero (undefined behavior, just return 0) + if (rem) *rem = 0; + return 0; + } + if (n.s.low == 0) { + if (rem) { + r.s.high = n.s.high % d.s.high; + r.s.low = 0; + *rem = r.all; + } + return n.s.high / d.s.high; + } + // n.s.high != 0 && n.s.low != 0 && d.s.low == 0 && d.s.high != 0 + if ((d.s.high & (d.s.high - 1)) == 0) { + // d is a power of 2 + if (rem) { + r.s.low = n.s.low; + r.s.high = n.s.high & (d.s.high - 1); + *rem = r.all; + } + return n.s.high >> __clzdi2(d.s.high); + } + sr = __clzdi2(d.s.high) - __clzdi2(n.s.high); + if (sr > n_udword_bits - 2) { + if (rem) *rem = n.all; + return 0; + } + ++sr; + // 1 <= sr <= n_udword_bits - 1 + q.s.low = 0; + q.s.high = n.s.low << (n_udword_bits - sr); + r.s.high = n.s.high >> sr; + r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); + goto shift_loop; + } + // d.s.low != 0 + if (d.s.high == 0) { + if ((d.s.low & (d.s.low - 1)) == 0) { + if (rem) *rem = n.s.low & (d.s.low - 1); + if (d.s.low == 1) return n.all; + sr = __builtin_ctzll(d.s.low); + q.s.high = n.s.high >> sr; + q.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); + return q.all; + } + sr = 1 + n_udword_bits + __clzdi2(d.s.low) - __clzdi2(n.s.high); + if (sr == n_udword_bits) { + q.s.low = 0; + q.s.high = 0; + r.s.high = 0; + r.s.low = n.s.high; + } else if (sr < n_udword_bits) { + q.s.low = 0; + q.s.high = n.s.low << (n_udword_bits - sr); + r.s.high = n.s.high >> sr; + r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); + } else { + q.s.low = n.s.low << (n_utword_bits - sr); + q.s.high = (n.s.high << (n_utword_bits - sr)) | (n.s.low >> (sr - n_udword_bits)); + r.s.high = 0; + r.s.low = n.s.high >> (sr - n_udword_bits); + } + goto shift_loop; + } + // d.s.high != 0 && d.s.low != 0 + sr = __clzdi2(d.s.high) - __clzdi2(n.s.high); + if (sr > n_udword_bits - 1) { + if (rem) *rem = n.all; + return 0; + } + ++sr; + q.s.low = 0; + if (sr == n_udword_bits) { + q.s.high = 0; + r.s.high = 0; + r.s.low = n.s.high; + } else { + r.s.high = n.s.high >> sr; + r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); + q.s.high = n.s.low << (n_udword_bits - sr); + } + +shift_loop: + // The main shift-and-subtract loop + for (; sr > 0; --sr) { + r.s.high = (r.s.high << 1) | (r.s.low >> (n_udword_bits - 1)); + r.s.low = (r.s.low << 1) | (q.s.high >> (n_udword_bits - 1)); + q.s.high = (q.s.high << 1) | (q.s.low >> (n_udword_bits - 1)); + q.s.low = (q.s.low << 1); + // Compute (r - d) and conditionally subtract + const long long s = (long long)(d.all - r.all - 1) >> (n_utword_bits - 1); + q.s.low |= s & 1; + r.all -= d.all & s; + } + if (rem) *rem = r.all; + return q.all; +} + +tu_int __udivti3(tu_int a, tu_int b) { + return __udivmodti4(a, b, 0); +} + +tu_int __umodti3(tu_int a, tu_int b) { + tu_int r; + __udivmodti4(a, b, &r); + return r; +} + +#endif // __SIZEOF_INT128__ diff --git a/vlib/fasthttp/fasthttp.v b/vlib/fasthttp/fasthttp.v index 58341c1df..26aa875a5 100644 --- a/vlib/fasthttp/fasthttp.v +++ b/vlib/fasthttp/fasthttp.v @@ -114,9 +114,10 @@ pub fn (h ServerHandle) wait_till_running(params WaitTillRunningParams) !int { } $if windows { return error('fasthttp server lifecycle control is not supported on windows') + } $else { + mut server := unsafe { &Server(h.ptr) } + return server.wait_till_running_impl(params)! } - mut server := unsafe { &Server(h.ptr) } - return server.wait_till_running_impl(params)! } // shutdown gracefully stops accepting new requests and waits for active requests to finish. @@ -126,9 +127,10 @@ pub fn (h ServerHandle) shutdown(params ShutdownParams) ! { } $if windows { return error('fasthttp server lifecycle control is not supported on windows') + } $else { + mut server := unsafe { &Server(h.ptr) } + server.shutdown_impl(params)! } - mut server := unsafe { &Server(h.ptr) } - server.shutdown_impl(params)! } $if !windows { diff --git a/vlib/js/dom/dom.js.v b/vlib/js/dom/dom.js.v index 74654d6c7..f84c55f6d 100644 --- a/vlib/js/dom/dom.js.v +++ b/vlib/js/dom/dom.js.v @@ -631,7 +631,7 @@ pub interface JS.WebGLRenderingContext { drawingBufferHeight JS.Number drawingBufferWidth JS.Number activeTexture(texture JS.Number) - attachShader(program JS.WebGLProgram, shader JS.WebGLProgram) + attachShader(program JS.WebGLProgram, shader JS.WebGLShader) linkProgram(program JS.WebGLProgram) bindAttribLocation(program JS.WebGLProgram, index JS.Number, name JS.String) bindBuffer(target JS.Number, buffer JS.WebGLBuffer) diff --git a/vlib/json/tests/json_encode_json2_any_test.v b/vlib/json/tests/json_encode_json2_any_test.v deleted file mode 100644 index a6d9f731e..000000000 --- a/vlib/json/tests/json_encode_json2_any_test.v +++ /dev/null @@ -1,29 +0,0 @@ -import json -import x.json2 - -fn test_encode_map_with_json2_any_values() { - data := { - 'array': json2.Any([json2.Any('x'), json2.Any(false)]) - 'bool': json2.Any(true) - 'number': json2.Any(f64(1.5)) - 'object': json2.Any({ - 'nested': json2.Any('ok') - }) - 'string': json2.Any('hello') - } - - encoded := json.encode(data) - decoded := json2.decode[map[string]json2.Any](encoded)! - array := decoded['array'] or { panic('missing array') } - bool_val := decoded['bool'] or { panic('missing bool') } - number := decoded['number'] or { panic('missing number') } - object := decoded['object'] or { panic('missing object') } - nested := object.as_map()['nested'] or { panic('missing nested') } - string_val := decoded['string'] or { panic('missing string') } - assert array.as_array()[0].str() == 'x' - assert array.as_array()[1].bool() == false - assert bool_val.bool() == true - assert number.f64() == 1.5 - assert nested.str() == 'ok' - assert string_val.str() == 'hello' -} diff --git a/vlib/orm/orm.v b/vlib/orm/orm.v index 7f00aa1f2..aa7bbcfdb 100644 --- a/vlib/orm/orm.v +++ b/vlib/orm/orm.v @@ -1103,7 +1103,7 @@ fn sql_field_name(field TableField) string { fn sql_field_select_expr(field TableField) string { for attr in field.attrs { if attr.name == 'sql_select' && attr.has_arg { - return attr.arg + return trim_attr_arg(attr.arg) } } return sql_field_name(field) diff --git a/vlib/sokol/sgl/sgl_test.v b/vlib/sokol/sgl/sgl_test.v index d0ac9e56a..147787bb8 100644 --- a/vlib/sokol/sgl/sgl_test.v +++ b/vlib/sokol/sgl/sgl_test.v @@ -1,3 +1,4 @@ +// vtest build: !docker-ubuntu-musl // needs GL/gl.h module sgl fn test_next_draw_chunk_keeps_uncapped_draws_intact() { diff --git a/vlib/strconv/format_mem.c.v b/vlib/strconv/format_mem.c.v index 9508c2551..2249b8500 100644 --- a/vlib/strconv/format_mem.c.v +++ b/vlib/strconv/format_mem.c.v @@ -139,7 +139,8 @@ pub fn format_dec_sb(d u64, p BF_param, mut res strings.Builder) { pub fn f64_to_str_lnd1(f f64, dec_digit int) string { unsafe { // we add the rounding value - s := f64_to_str(f + dec_round[dec_digit], 18) + clamped_dec := if dec_digit >= dec_round.len { dec_round.len - 1 } else { dec_digit } + s := f64_to_str(f + dec_round[clamped_dec], 18) // check for +inf -inf Nan if s.len > 2 && (s[0] == `n` || s[1] == `i`) { return s diff --git a/vlib/toml/parser/parser.v b/vlib/toml/parser/parser.v index 84ca1f53b..635ad26f9 100644 --- a/vlib/toml/parser/parser.v +++ b/vlib/toml/parser/parser.v @@ -980,38 +980,40 @@ pub fn (mut p Parser) double_array_of_tables(mut table map[string]ast.Value) ! { mut t_map := ast.Value(ast.Null{}) unsafe { - if table_first := table[first.str()] { - if table_first is map[string]ast.Value { - mut t := &(table_first as map[string]ast.Value) - if val := t[last.str()] { - if val is []ast.Value { - mut arr := &val - arr << p.array_of_tables_contents()! - t[last.str()] = arr + if dotted_key.len == 2 { + if table_first := table[first.str()] { + if table_first is map[string]ast.Value { + mut t := &(table_first as map[string]ast.Value) + if val := t[last.str()] { + if val is []ast.Value { + mut arr := &val + arr << p.array_of_tables_contents()! + t[last.str()] = arr + } else { + return error(@MOD + '.' + @STRUCT + '.' + @FN + + ' t[${last.str()}] is not an array. (excerpt): "...${p.excerpt()}..."') + } } else { - return error(@MOD + '.' + @STRUCT + '.' + @FN + - ' t[${last.str()}] is not an array. (excerpt): "...${p.excerpt()}..."') + t[last.str()] = p.array_of_tables_contents()! } - } else { - t[last.str()] = p.array_of_tables_contents()! + p.last_aot.clear() + p.last_aot_index = 0 + return } + } else { + util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'implicit allocation of map for `${first}` in dotted key `${dotted_key}`.') + mut t := &map[string]ast.Value{} + p.implicit_declared << first + // NOTE: We register this implicit allocation also as *explicit* to be able to catch a special case like: + // https://github.com/toml-lang/toml-test/blob/576db852/tests/invalid/table/array-implicit.toml + // See also: undo_special_case_01 + p.explicit_declared << first + t[last.str()] = p.array_of_tables_contents()! + table[first.str()] = ast.Value(t) p.last_aot.clear() p.last_aot_index = 0 return } - } else { - util.printdbg(@MOD + '.' + @STRUCT + '.' + @FN, 'implicit allocation of map for `${first}` in dotted key `${dotted_key}`.') - mut t := &map[string]ast.Value{} - p.implicit_declared << first - // NOTE: We register this implicit allocation also as *explicit* to be able to catch a special case like: - // https://github.com/toml-lang/toml-test/blob/576db852/tests/invalid/table/array-implicit.toml - // See also: undo_special_case_01 - p.explicit_declared << first - t[last.str()] = p.array_of_tables_contents()! - table[first.str()] = ast.Value(t) - p.last_aot.clear() - p.last_aot_index = 0 - return } // NOTE this is starting to get EVEN uglier. TOML is not *at all* simple at this point... diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 2a9790692..2677682de 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1951,6 +1951,11 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str } } } + if param.orig_typ.has_flag(.generic) { + if otyp := t.convert_generic_type(param.orig_typ, generic_names, to_types) { + param.orig_typ = otyp + } + } } func.name = '' func.generic_names = [] @@ -2154,6 +2159,36 @@ pub fn (mut t Table) convert_generic_param_type(param Param, generic_names []str return t.convert_generic_type(param.typ, generic_names, to_types) } +// type_contains_placeholder returns true if the given type or any of its inner +// generic types resolves to a placeholder (i.e., an undefined/unknown type). +pub fn (t &Table) type_contains_placeholder(typ Type) bool { + sym := t.sym(typ) + if sym.kind == .placeholder { + return true + } + return match sym.info { + Array { + t.type_contains_placeholder(sym.info.elem_type) + } + ArrayFixed { + t.type_contains_placeholder(sym.info.elem_type) + } + Map { + t.type_contains_placeholder(sym.info.key_type) + || t.type_contains_placeholder(sym.info.value_type) + } + SumType { + sym.info.concrete_types.any(t.type_contains_placeholder(it)) + } + Struct { + sym.info.concrete_types.any(t.type_contains_placeholder(it)) + } + else { + false + } + } +} + pub fn (mut t Table) unwrap_generic_param_type(param Param, generic_names []string, concrete_types []Type) Type { if param.is_mut && param.orig_typ != 0 && param.orig_typ.has_flag(.generic) && concrete_types.all(!it.has_flag(.generic)) { @@ -2275,6 +2310,10 @@ pub fn (mut t Table) unwrap_generic_type_ex(typ Type, generic_names []string, co concrete_types) has_generic = true } + if param.orig_typ.has_flag(.generic) { + unwrapped_fn.params[i].orig_typ = t.unwrap_generic_type(param.orig_typ, + generic_names, concrete_types) + } } if unwrapped_fn.return_type.has_flag(.generic) { unwrapped_fn.return_type = t.unwrap_generic_type_ex(unwrapped_fn.return_type, @@ -2756,6 +2795,13 @@ pub fn (mut t Table) generic_insts_to_concrete() { param.typ = t_typ } } + if param.orig_typ.has_flag(.generic) { + if t_typ := t.convert_generic_type(param.orig_typ, function.generic_names, + info.concrete_types) + { + param.orig_typ = t_typ + } + } } if function.return_type.has_flag(.generic) { if t_typ := t.convert_generic_type(function.return_type, function.generic_names, diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index dc5dc2883..eac863796 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -1204,6 +1204,18 @@ fn (mut b Builder) cc_linux_cross() { } } } + // Compile compiler runtime builtins (provides __udivti3 etc. for 128-bit integer + // operations used by thirdparty code like mbedtls bignum.c, since the linuxroot + // sysroot doesn't include libgcc or compiler-rt). + builtins_src := os.join_path(@VEXEROOT, 'thirdparty', 'builtins', 'compiler_builtins.c') + builtins_obj := os.join_path(os.vtmp_dir(), 'compiler_builtins.o') + if os.exists(builtins_src) { + builtins_cmd := '${b.quote_compiler_name(cc_name)} -w -fPIC -target x86_64-linux-gnu -o ${os.quoted_path(builtins_obj)} -c ${os.quoted_path(builtins_src)}' + builtins_res := os.execute(builtins_cmd) + if builtins_res.exit_code != 0 { + println('Warning: failed to compile compiler builtins for cross compilation.') + } + } mut linker_args := [ '-L', os.quoted_path(stubs_dir), @@ -1232,6 +1244,9 @@ fn (mut b Builder) cc_linux_cross() { linker_args << libs linker_args << extra_obj_files linker_args << cflags.c_options_only_object_files() + if os.exists(builtins_obj) { + linker_args << os.quoted_path(builtins_obj) + } // -ldl b.dump_c_options(linker_args) mut ldlld := '${sysroot}/ld.lld' diff --git a/vlib/v/builder/compile.v b/vlib/v/builder/compile.v index c5ca50ce0..7ad1c4bad 100644 --- a/vlib/v/builder/compile.v +++ b/vlib/v/builder/compile.v @@ -234,7 +234,7 @@ pub fn (mut v Builder) set_module_lookup_paths() { v.module_search_paths << v.compiled_dir mut source_root := '' src_root := os.join_path(v.compiled_dir, 'src') - if os.exists(src_root) { + if os.exists(src_root) && !v.pref.is_vsh { root_files := os.ls(v.compiled_dir) or { []string{} } if v.pref.should_compile_filtered_files(v.compiled_dir, root_files).len == 0 { source_root = src_root diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 40f1c2bc0..beb306b3a 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -1196,16 +1196,19 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { } } } - if arg.expr.is_auto_deref_var() && param_infer_typ.nr_muls() > 0 { + if arg.expr.is_auto_deref_var() && typ.is_ptr() { typ = typ.deref() } if has_concrete_caller_types && param.is_mut && param_infer_typ.nr_muls() == 0 - && typ.is_ptr() && c.table.final_sym(typ).kind == .struct { + && typ.is_ptr() && c.table.final_sym(c.unwrap_generic(typ)).kind == .struct { typ = typ.deref() } // resolve &T &&T ... - if param_infer_typ.nr_muls() > 0 && typ.nr_muls() > 0 { - param_muls := param_infer_typ.nr_muls() + // Use param.typ (not param_infer_typ) to get the actual pointer + // count including mut lowering, so that e.g. `mut val T` with + // param.typ=&T correctly strips the pointer from the arg type. + if param.typ.nr_muls() > 0 && typ.nr_muls() > 0 { + param_muls := param.typ.nr_muls() arg_muls := typ.nr_muls() typ = if arg_muls >= param_muls { typ.set_nr_muls(arg_muls - param_muls) @@ -1338,8 +1341,14 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { continue } typ = if has_concrete_caller_types && cur_param.typ.has_flag(.generic) { - c.table.unwrap_generic_param_type(cur_param, c.table.cur_fn.generic_names, - c.table.cur_concrete_types) + if cur_param.is_mut && cur_param.orig_typ != 0 + && cur_param.orig_typ.has_flag(.generic) { + c.table.unwrap_generic_type(cur_param.orig_typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + } else { + c.table.unwrap_generic_param_type(cur_param, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + } } else { cur_param.typ } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 58e38653d..00a5b57bb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4235,7 +4235,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { // if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated // Do not allow `&u8(unsafe { nil })` etc, force nil or voidptr cast if from_type.is_number() && to_type.is_ptr() && !c.inside_unsafe && !c.pref.translated - && !c.file.is_translated { + && !c.file.is_translated && to_sym.kind != .sum_type { if from_sym.language != .c { ne_name := node.expr.str() if !ne_name.starts_with('C.') { @@ -4485,7 +4485,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } } } - if node.expr_type == ast.string_type_idx { + if node.expr_type == ast.string_type_idx && !c.skip_flags { c.add_error_detail('use ${c.table.type_to_str(node.typ)}.from_string(${node.expr}) instead') c.error('cannot cast `string` to `enum`', node.pos) } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 17e56a11a..100bb2c07 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -108,22 +108,6 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { return } node.ninstances++ - mut old_params := []ast.Param{} - if node.generic_names.len > 0 && c.table.cur_concrete_types.len == node.generic_names.len - && c.table.cur_concrete_types.all(!it.has_flag(.generic)) { - old_params = node.params.clone() - for i, param in old_params { - if !param.typ.has_flag(.generic) { - continue - } - if typ := c.table.convert_generic_param_type(param, node.generic_names, c.table.cur_concrete_types) { - node.params[i].typ = typ - if mut v := node.scope.find_var(param.name) { - v.typ = typ - } - } - } - } // save all the state that fn_decl or inner statements/expressions // could potentially modify, since functions can be nested, due to // anonymous function support, and ensure that it is restored, when @@ -141,14 +125,6 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.inside_unsafe = node.is_unsafe c.returns = false defer { - if old_params.len > 0 { - node.params = old_params - for param in old_params { - if mut v := node.scope.find_var(param.name) { - v.typ = param.typ - } - } - } c.stmt_level = prev_stmt_level c.fn_level-- c.returns = prev_returns diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index c87e6d940..15982b297 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -282,8 +282,8 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { branch.scope.objects[var.name] = w } branch.scope.update_var_type(var.name, unwrapped_guard_typ) - if !unwrapped_guard_typ.is_ptr() - && c.table.sym(unwrapped_guard_typ).is_heap() { + resolved_guard_typ := c.unwrap_generic(unwrapped_guard_typ) + if !resolved_guard_typ.is_ptr() && c.table.sym(resolved_guard_typ).is_heap() { if mut guard_var := branch.scope.find_var(var.name) { guard_var.is_auto_heap = true } diff --git a/vlib/v/checker/tests/modules/deprecated_module.out b/vlib/v/checker/tests/modules/deprecated_module.out index 20974d76f..ed74c4f39 100644 --- a/vlib/v/checker/tests/modules/deprecated_module.out +++ b/vlib/v/checker/tests/modules/deprecated_module.out @@ -1,10 +1,10 @@ -vlib/v/checker/tests/modules/deprecated_module/main.v:2:1: notice: module `www.ttt` will be deprecated after 2999-01-01, and will become an error after 2999-06-30; use xxx.yyy +vlib/v/checker/tests/modules/deprecated_module/main.v:2:1: notice: module `deprecated_module.www.ttt` will be deprecated after 2999-01-01, and will become an error after 2999-06-30; use xxx.yyy 1 | import bbb.ccc 2 | import www.ttt | ~~~~~~~~~~~~~~ 3 | import xxx.yyy 4 | -vlib/v/checker/tests/modules/deprecated_module/main.v:12:11: error: undefined ident: `www.ttt.non_existing` +vlib/v/checker/tests/modules/deprecated_module/main.v:12:11: error: undefined ident: `deprecated_module.www.ttt.non_existing` 10 | dump(ttt.f()) 11 | dump(yyy.f()) 12 | dump(ttt.non_existing) diff --git a/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out b/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out index 71397d1d9..41187f042 100644 --- a/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out +++ b/vlib/v/checker/tests/os_file_read_struct_non_struct_err.out @@ -5,9 +5,9 @@ vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv:21:23: error: `os.Fil | ~~~~~~~~~ 22 | file.read_struct_at[MyInterface](mut iface_value, 0) or { panic(err) } 23 | } -vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv:22:39: error: `os.File.read_struct_at` expects a struct type, but got `MyInterface` +vlib/v/checker/tests/os_file_read_struct_non_struct_err.vv:22:21: error: `os.File.read_struct_at` expects a struct type, but got `MyInterface` 20 | mut iface_value := MyInterface(MyStruct{val: 1}) 21 | file.read_struct(mut sum_value) or { panic(err) } 22 | file.read_struct_at[MyInterface](mut iface_value, 0) or { panic(err) } - | ~~~~~~~~~~~ + | ~~~~~~~~~~~~~ 23 | } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index d1990e6a2..127394603 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -202,8 +202,7 @@ mut: anon_fns shared []string // remove duplicate anon generated functions sumtype_definitions map[u32]bool // `_TypeA_to_sumtype_TypeB()` fns that have been generated trace_fn_definitions []string - json_types []ast.Type // to avoid json gen duplicates - json_types_modes map[ast.Type]JsonGenKind + json_types []ast.Type // to avoid json gen duplicates pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name hotcode_fn_names []string hotcode_fpaths []string @@ -350,7 +349,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO shared_types: strings.new_builder(100) shared_functions: strings.new_builder(100) json_forward_decls: strings.new_builder(100) - json_types_modes: map[ast.Type]JsonGenKind{} sql_buf: strings.new_builder(100) table: table pref: pref_ @@ -486,9 +484,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.array_last_index_types << g.array_last_index_types global_g.pcs << g.pcs global_g.json_types << g.json_types - for typ, mode in g.json_types_modes { - global_g.json_types_modes[typ] = global_g.json_types_modes[typ] | mode - } global_g.hotcode_fn_names << g.hotcode_fn_names global_g.hotcode_fpaths << g.hotcode_fpaths global_g.test_function_names << g.test_function_names @@ -858,7 +853,6 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen { channel_definitions: strings.new_builder(100) thread_definitions: strings.new_builder(100) json_forward_decls: strings.new_builder(100) - json_types_modes: map[ast.Type]JsonGenKind{} enum_typedefs: strings.new_builder(100) sql_buf: strings.new_builder(100) cleanup: strings.new_builder(100) @@ -4588,6 +4582,16 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { if name_type == ast.void_type_idx { name_type = node.name_type } + // For mut params, typeof_type strips the pointer for comptime, + // but typeof(x).name should show the actual pointer type. + if node.expr.expr is ast.Ident { + if node.expr.expr.obj is ast.Var { + if node.expr.expr.obj.is_auto_deref + && node.expr.expr.obj.typ.is_ptr() { + name_type = name_type.ref() + } + } + } } g.type_name(name_type) return @@ -4597,6 +4601,15 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { if node.expr is ast.TypeOf { name_type = g.type_resolver.typeof_field_type(g.type_resolver.typeof_type(node.expr.expr, g.resolve_typeof_expr_type(node.expr.expr, name_type)), node.field_name) + // For mut params (auto_deref), strip pointer so that + // typeof(mut_param).idx == typeof(val_param).idx + if node.expr.expr is ast.Ident { + if node.expr.expr.obj is ast.Var { + if node.expr.expr.obj.is_auto_deref && name_type.is_ptr() { + name_type = name_type.deref() + } + } + } g.write(int(name_type).str()) } else { g.write(int(g.unwrap_generic(name_type)).str()) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5a4d3e481..b25c71367 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -108,6 +108,7 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) { g.gen_attrs(node.attrs) mut skip := false pos := g.out.len + defs_pos := g.definitions.len should_bundle_module := util.should_bundle_module(node.mod) if g.pref.build_mode == .build_module { // TODO: true for not just "builtin" @@ -157,6 +158,9 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) { g.fn_decl = keep_fn_decl if skip { g.go_back_to(pos) + if g.pref.build_mode != .build_module && !g.pref.use_cache { + g.definitions.go_back_to(defs_pos) + } } if !g.pref.skip_unused { if node.language != .c { @@ -880,7 +884,8 @@ fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic typ = g.table.sym(typ).array_info().elem_type.set_flag(.variadic) } param_type_sym := g.table.sym(typ) - if param.is_mut && param.orig_typ != 0 && param.orig_typ.has_flag(.generic) { + if param.is_mut && param.orig_typ != 0 && param.orig_typ.has_flag(.generic) + && param.typ.has_flag(.generic) { mut surface_typ := g.unwrap_generic(param.orig_typ) typ = if surface_typ.is_ptr() && g.table.sym(surface_typ).kind == .struct { surface_typ.ref() @@ -2119,7 +2124,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { cur_line := g.go_before_last_stmt() if is_json_encode || is_json_encode_pretty { unwrapped_typ := g.unwrap_generic(node.args[0].typ) - g.gen_json_for_type(unwrapped_typ, .encode) + g.gen_json_for_type(unwrapped_typ) json_type_str = g.styp(unwrapped_typ) // `json__encode` => `json__encode_User` encode_name := js_enc_name(json_type_str) @@ -2143,7 +2148,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { // `json.decode(User, s)` => json.decode_User(s) typ := c_name(g.styp(ast_type.typ)) fn_name := c_fn_name(name) + '_' + typ - g.gen_json_for_type(ast_type.typ, .decode) + g.gen_json_for_type(ast_type.typ) g.empty_line = true g.writeln('// json.decode') g.write('cJSON* ${json_obj} = json__json_parse(') diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index cb9ba4397..fc041612a 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -412,11 +412,22 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } else { branch.cond.vars[0].name } + mut short_opt_is_auto_heap := false + if branch.stmts.len > 0 { + scope := g.file.scope.innermost(ast.Node(branch.stmts.last()).pos().pos) + if v := scope.find_var(branch.cond.vars[0].name) { + short_opt_is_auto_heap = v.is_auto_heap + } + } if g.table.sym(branch.cond.expr_type).kind == .array_fixed { g.writeln('\t${base_type} ${cond_var_name} = {0};') g.write('\tmemcpy((${base_type}*)${cond_var_name}, &') g.expr(branch.cond.expr) g.writeln(', sizeof(${base_type}));') + } else if short_opt_is_auto_heap { + g.write('\t${base_type}* ${cond_var_name} = HEAP(${base_type}, ') + g.expr(branch.cond.expr) + g.writeln(');') } else { g.write('\t${base_type} ${cond_var_name} = ') g.expr(branch.cond.expr) diff --git a/vlib/v/gen/c/or_block_line_info_test.v b/vlib/v/gen/c/or_block_line_info_test.v index 394474f39..9aeb82234 100644 --- a/vlib/v/gen/c/or_block_line_info_test.v +++ b/vlib/v/gen/c/or_block_line_info_test.v @@ -14,8 +14,12 @@ fn test_option_propagation_panic_has_matching_line_info() { res := os.execute(cmd) assert res.exit_code == 0, '${cmd}\n${res.output}' lines := res.output.replace('\r\n', '\n').split_into_lines() - expected_line := '#line 12 "${source_path}"' - expected_panic := 'builtin__panic_debug(12, builtin__tos3("${source_path}")' + // On Windows, #line directives use double-escaped backslashes, while + // panic_debug uses forward slashes, so normalize paths for comparison. + escaped_source_path := source_path.replace('\\', '\\\\') + fwd_source_path := source_path.replace('\\', '/') + expected_line := '#line 12 "${escaped_source_path}"' + expected_panic := 'builtin__panic_debug(12, builtin__tos3("${fwd_source_path}")' mut main_idx := -1 for i, line in lines { if line.contains('void main__main(void) {') { diff --git a/vlib/v/gen/c/orm.v b/vlib/v/gen/c/orm.v index 4c01b0d08..c17303723 100644 --- a/vlib/v/gen/c/orm.v +++ b/vlib/v/gen/c/orm.v @@ -1500,7 +1500,12 @@ fn (g &Gen) get_orm_column_name_from_struct_field(field ast.StructField) string // get_orm_select_expr_from_struct_field returns the SQL expression used in a SELECT list. fn (g &Gen) get_orm_select_expr_from_struct_field(field ast.StructField) string { if attr := field.attrs.find_first('sql_select') { - return attr.arg + arg := attr.arg.trim_space() + if arg.len >= 2 && ((arg.starts_with("'") && arg.ends_with("'")) + || (arg.starts_with('"') && arg.ends_with('"'))) { + return arg[1..arg.len - 1].trim_space() + } + return arg } return g.get_orm_column_name_from_struct_field(field) } diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 0a8e5ac27..c1f7c54f4 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -421,6 +421,8 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { } else if i < node_.expr_types.len && g.table.final_sym(g.unwrap_generic(expr.obj.typ)).kind in [.interface, .sum_type] { node_.expr_types[i] + } else if i < node_.expr_types.len { + node_.expr_types[i] } else { g.type_resolver.get_type_or_default(expr, expr.obj.typ) } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 725aee789..5455e0413 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -474,6 +474,7 @@ pub fn (mut g JsGen) init() { } else { g.definitions.writeln('const \$os = require("os");') g.definitions.writeln('const \$process = process;') + g.definitions.writeln('if (typeof print === "undefined") { globalThis.print = function() {}; }') } g.definitions.writeln('function checkDefine(key) {') g.definitions.writeln('\tif (globalThis.hasOwnProperty(key)) { return !!globalThis[key]; } return false;') diff --git a/vlib/v/gen/js/tests/global_export.v b/vlib/v/gen/js/tests/global_export.v index b1d5f2467..0f1ebcce8 100644 --- a/vlib/v/gen/js/tests/global_export.v +++ b/vlib/v/gen/js/tests/global_export.v @@ -7,7 +7,7 @@ __global tic = fn (a int) int { } fn main() { - assert int(JS.eval(c'(globalThis.TIC === globalThis.tic) ? 1 : 0')) == 1 - assert int(JS.eval(c'typeof globalThis.TIC === "function" ? globalThis.TIC(4) : -1')) == 5 + assert int(JS.eval(js'(globalThis.TIC === globalThis.tic) ? 1 : 0')) == 1 + assert int(JS.eval(js'typeof globalThis.TIC === "function" ? globalThis.TIC(4) : -1')) == 5 assert tic(2) == 3 } diff --git a/vlib/v/generics/new_generics_regression_test.v b/vlib/v/generics/new_generics_regression_test.v index 31754bfe8..1e465ff7d 100644 --- a/vlib/v/generics/new_generics_regression_test.v +++ b/vlib/v/generics/new_generics_regression_test.v @@ -79,8 +79,8 @@ fn run_new_generic_solver_tests(root_label string, test_cmd string, expected_sum println('') } -const expected_summsvc_generics = 'Summary for all V _test.v files: 53 failed, 213 passed, 266 total.' -const expected_summary_generics = 'Summary for all V _test.v files: 52 failed, 214 passed, 266 total.' +const expected_summsvc_generics = 'Summary for all V _test.v files: 55 failed, 212 passed, 267 total.' +const expected_summary_generics = 'Summary for all V _test.v files: 54 failed, 213 passed, 267 total.' const expected_summsvc_vec = 'Summary for all V _test.v files: 3 failed, 3 total.' const expected_summary_vec = 'Summary for all V _test.v files: 3 failed, 3 total.' const expected_summsvc_flag = 'Summary for all V _test.v files: 14 failed, 5 passed, 19 total.' @@ -100,9 +100,11 @@ const failing_tests = [ 'vlib/v/tests/generics/generic_fn_infer_multi_paras_test.v', 'vlib/v/tests/generics/generic_fn_type_with_different_generic_type_test.v', 'vlib/v/tests/generics/generic_fn_typeof_name_test.v', + 'vlib/v/tests/generics/generic_fn_value_inference_test.v', 'vlib/v/tests/generics/generic_fn_with_comptime_for_test.v', 'vlib/v/tests/generics/generic_map_alias_test.v', 'vlib/v/tests/generics/generic_muls_test.v', + 'vlib/v/tests/generics/generic_mut_pointer_param_test.v', 'vlib/v/tests/generics/generic_operator_overload_test.v', 'vlib/v/tests/generics/generic_options_with_reserved_ident_test.v', 'vlib/v/tests/generics/generic_receiver_embed_test.v', diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 128533581..d64f467e2 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -963,12 +963,6 @@ pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) { if w.trace_enabled { w.level++ defer(fn) { w.level-- } - receiver_name := if node.is_method && node.receiver.typ != 0 { - w.table.type_to_str(node.receiver.typ) + '.' - } else { - '' - } - eprintln('>>>${' '.repeat(w.level)}${receiver_name}${node.name}') } if node.is_closure { w.used_closures++ @@ -982,6 +976,39 @@ pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) { } w.mark_fn_ret_and_params(node.return_type, node.params) w.mark_fn_as_used(fkey) + // For generic functions, mark the concrete param/return types for all instantiations. + // This is needed because mark_fn_ret_and_params skips types with .generic flag, + // but the cgen will emit concrete versions for all fn_generic_types entries. + if node.generic_names.len > 0 { + max_param_len := if node.is_method { node.params.len - 1 } else { node.params.len } + param_i := if node.is_method { 1 } else { 0 } + for concrete_type_list in w.table.fn_generic_types[fkey] { + // mark concrete return type + if node.return_type.has_flag(.generic) { + if resolved := w.table.convert_generic_type(node.return_type, node.generic_names, + concrete_type_list) + { + w.mark_by_type(resolved) + } + } + // mark concrete param types + for k, concrete_type in concrete_type_list { + if k >= max_param_len { + break + } + param_typ := node.params[k + param_i].typ + if param_typ.has_flag(.generic) { + if resolved := w.table.convert_generic_type(param_typ, node.generic_names, + concrete_type_list) + { + w.mark_by_type(resolved) + } else if w.table.type_kind(param_typ) == .array { + w.mark_by_type(w.table.find_or_register_array(concrete_type)) + } + } + } + } + } w.cur_fn = fkey w.stmts(node.stmts) w.defer_stmts(node.defer_stmts) @@ -1129,7 +1156,7 @@ pub fn (mut w Walker) call_expr(mut node ast.CallExpr) { } if ((node.is_method && stmt.params.len > 1) || !node.is_method) && stmt.generic_names.len > 0 { - // mark concrete []T param as used + // mark concrete generic param types (e.g. []T, ...Node[T]) as used max_param_len := if node.is_method { stmt.params.len - 1 } else { stmt.params.len } param_i := if node.is_method { 1 } else { 0 } for concrete_type_list in w.table.fn_generic_types[fn_name] { @@ -1139,7 +1166,11 @@ pub fn (mut w Walker) call_expr(mut node ast.CallExpr) { } param_typ := stmt.params[k + param_i].typ if param_typ.has_flag(.generic) { - if w.table.type_kind(param_typ) == .array { + if resolved := w.table.convert_generic_type(param_typ, stmt.generic_names, + concrete_type_list) + { + w.mark_by_type(resolved) + } else if w.table.type_kind(param_typ) == .array { w.mark_by_type(w.table.find_or_register_array(concrete_type)) } else if param_typ.has_flag(.option) { w.used_option++ diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 171144b6d..7e6817443 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -1332,7 +1332,8 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { if is_mut { p.next() } - if p.fn_language == .c && is_ident_name(p.tok.lit) + if p.fn_language == .c && (is_ident_name(p.tok.lit) || (p.tok.lit.len > 1 + && p.tok.lit[0] == `@` && is_ident_name(p.tok.lit[1..]))) && p.tok.kind !in [.key_fn, .key_struct] && p.peek_tok.kind !in [.comma, .rpar, .dot] { name = p.tok.lit diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index cdda9b7bc..a718ad4f7 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2309,19 +2309,20 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { } } mcall_expr := ast.CallExpr{ - left: left - name: field_name - kind: p.call_kind(field_name) - args: args - name_pos: name_pos - pos: pos - is_method: true - concrete_types: concrete_types - concrete_list_pos: concrete_list_pos - or_block: or_block - scope: p.scope - comments: comments - is_return_used: p.expecting_value + left: left + name: field_name + kind: p.call_kind(field_name) + args: args + name_pos: name_pos + pos: pos + is_method: true + concrete_types: concrete_types + concrete_list_pos: concrete_list_pos + raw_concrete_types: concrete_types + or_block: or_block + scope: p.scope + comments: comments + is_return_used: p.expecting_value } return mcall_expr } diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 510a6e69b..a5370dcdf 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -195,7 +195,9 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { is_field_pub = false is_field_mut = true is_field_global = false - } else if p.tok.kind == .key_mut { + } else if p.tok.kind == .key_mut && p.peek_tok.kind == .name + && p.peek_token(2).line_nr == p.tok.line_nr + && p.peek_token(2).kind !in [.assign, .rcbr, .semicolon] { p.error_with_pos('missing `:` after `mut` in struct', p.tok.pos()) return ast.StructDecl{} } else if p.tok.kind == .key_global && p.peek_tok.kind == .colon { diff --git a/vlib/v/tests/generics/generic_operator_overload_test.v b/vlib/v/tests/generics/generic_operator_overload_test.v index 4aa24ad6a..d7a7d636a 100644 --- a/vlib/v/tests/generics/generic_operator_overload_test.v +++ b/vlib/v/tests/generics/generic_operator_overload_test.v @@ -17,7 +17,11 @@ fn (m1 Matrix[T]) + (m2 Matrix[T]) Matrix[T] { if m1.row != m2.row || m1.col != m2.col { panic('Addition can only be performed on matrix with same size') } - mut res := m1 + mut res := Matrix[T]{ + row: m1.row + col: m1.col + data: m1.data.clone() + } for i in 0 .. m2.row { for j in 0 .. m2.col { res.data[i][j] += m2.data[i][j] @@ -48,7 +52,7 @@ fn test_generic_operator_overload() { println(a1) assert a1.row == 2 assert a1.col == 3 - assert a1.data == [[15, 18, 21], [24, 27, 30]] + assert a1.data == [[8, 10, 12], [14, 16, 18]] eq_ret := a1 == a2 println(eq_ret) diff --git a/vlib/v/util/module.v b/vlib/v/util/module.v index a6bd6762c..16ecae5cd 100644 --- a/vlib/v/util/module.v +++ b/vlib/v/util/module.v @@ -34,8 +34,14 @@ pub fn qualify_import(pref_ &pref.Preferences, mod string, file_path string) str } } } - if m1 := mod_path_to_full_name(pref_, mod, file_path) { - trace_qualify(@FN, mod, file_path, 'import_res 2', m1, file_path) + // Use absolute file_path so mod_path_to_full_name can walk up to find v.mod + abs_file_path := if os.is_abs_path(file_path) { + file_path + } else { + os.join_path_single(os.getwd(), file_path) + } + if m1 := mod_path_to_full_name(pref_, mod, abs_file_path) { + trace_qualify(@FN, mod, file_path, 'import_res 2', m1, abs_file_path) // > qualify_module: analyzer | file_path: /v/vls/analyzer/store.v | => module_res 2: analyzer ; clean_file_path - getwd == mod // > qualify_import: analyzer.depgraph | file_path: /v/vls/analyzer/store.v | => import_res 2: analyzer.depgraph ; /v/vls/analyzer/store.v // > qualify_import: tree_sitter | file_path: /v/vls/analyzer/store.v | => import_res 2: tree_sitter ; /v/vls/analyzer/store.v @@ -102,13 +108,6 @@ pub fn qualify_module(pref_ &pref.Preferences, mod string, file_path string) str // 2022-01-30 it leads to path differences, and the / version on windows triggers a module lookip bug, // 2022-01-30 leading to completely different errors) fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !string { - normalized_path := os.real_path(path) - normalized_pref_path := os.real_path(pref_.path) - normalized_pref_base := if os.is_dir(normalized_pref_path) { - normalized_pref_path - } else { - normalized_pref_path.all_before_last(os.path_separator) - } // TODO: explore using `pref.lookup_path` & `os.vmodules_paths()` // absolute paths instead of 'vlib' & '.vmodules' mut vmod_folders := ['vlib', '.vmodules', 'modules'] @@ -119,14 +118,14 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri } } mut in_vmod_path := false - parts := normalized_path.split(os.path_separator) + parts := path.split(os.path_separator) for vmod_folder in vmod_folders { if vmod_folder in parts { in_vmod_path = true break } } - path_parts := normalized_path.split(os.path_separator) + path_parts := path.split(os.path_separator) mod_path := mod.replace('.', os.path_separator) // go back through each parent in path_parts and join with `mod_path` to see the dir exists for i := path_parts.len - 1; i > 0; i-- { @@ -170,14 +169,27 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri } } } - if os.is_abs_path(normalized_pref_base) && os.is_abs_path(normalized_path) - && os.is_dir(normalized_path) && normalized_path.all_after_last(os.path_separator) == mod { // && path.contains(mod ) - rel_mod_path := normalized_path.replace(normalized_pref_base + os.path_separator, - '') - if rel_mod_path != normalized_path { - full_mod_name := rel_mod_path.replace(os.path_separator, '.') + if pref_.path.len > 0 && os.is_dir(path) { + real_pref_path_dir := pref_path_to_source_root(pref_) + real_path := os.real_path(path) + prefix := real_pref_path_dir + os.path_separator + if real_path.starts_with(prefix) { + full_mod_name := real_path[prefix.len..].replace(os.path_separator, '.') return full_mod_name } } return error('module not found') } + +fn pref_path_to_source_root(pref_ &pref.Preferences) string { + pref_path_dir := if os.is_dir(pref_.path) { pref_.path } else { os.dir(pref_.path) } + real_pref_path_dir := os.real_path(pref_path_dir) + files := os.ls(real_pref_path_dir) or { return real_pref_path_dir } + if pref_.should_compile_filtered_files(real_pref_path_dir, files).len == 0 { + src_path := os.join_path(real_pref_path_dir, 'src') + if os.is_dir(src_path) { + return src_path + } + } + return real_pref_path_dir +} diff --git a/vlib/v2/eval/eval.v b/vlib/v2/eval/eval.v index 9760915b1..dd5123af7 100644 --- a/vlib/v2/eval/eval.v +++ b/vlib/v2/eval/eval.v @@ -3130,7 +3130,10 @@ fn (mut e Eval) update_index_target(expr ast.IndexExpr, value Value) ! { if index < 0 || index >= container.values.len { return error('v2.eval: array index out of bounds') } - mut updated := container + mut updated := ArrayValue{ + ...container + values: container.values.clone() + } assigned_value := e.adapt_value_to_type_name(value, updated.elem_type_name) updated.values[index] = assigned_value e.update_target(expr.lhs, updated)! diff --git a/vlib/veb/tests/graceful_shutdown_test.v b/vlib/veb/tests/graceful_shutdown_test.v index 3c3fda350..90bb4579f 100644 --- a/vlib/veb/tests/graceful_shutdown_test.v +++ b/vlib/veb/tests/graceful_shutdown_test.v @@ -1,3 +1,10 @@ +// vfmt off +fn test_stub() { + $if !new_veb ? { + return + } +} + $if new_veb ? { import net import net.http -- 2.39.5