all: big batch4 fixes

This commit is contained in:
Alexander Medvednikov committed 2026-04-02 06:13:52 +03:00
1 parent 93e57bbe4f
commit eeaaff218b
40 files changed
+538 -163

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
// vtest retry: 3
import os
import v.vmod
+7 -3
View File
@@ -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')
+3
View File
@@ -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
+13
View File
@@ -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())
}
+33
View File
@@ -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
}
+29
View File
@@ -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())
}
+165
View File
@@ -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__
+6 -4
View File
@@ -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 {
+1 -1
View File
@@ -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)
@@ -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'
}
+1 -1
View File
@@ -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)
+1
View File
@@ -1,3 +1,4 @@
// vtest build: !docker-ubuntu-musl // needs GL/gl.h
module sgl
fn test_next_draw_chunk_keeps_uncapped_draws_intact() {
+2 -1
View File
@@ -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
+27 -25
View File
@@ -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...
+46
View File
@@ -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,
+15
View File
@@ -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'
+1 -1
View File
@@ -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
+15 -6
View File
@@ -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
}
+2 -2
View File
@@ -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)
}
-24
View File
@@ -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
+2 -2
View File
@@ -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
}
@@ -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)
@@ -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 | }
+20 -7
View File
@@ -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())
+8 -3
View File
@@ -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(')
+11
View File
@@ -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)
+6 -2
View File
@@ -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) {') {
+6 -1
View File
@@ -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)
}
+2
View File
@@ -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)
}
+1
View File
@@ -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;')
+2 -2
View File
@@ -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
}
@@ -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',
+39 -8
View File
@@ -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++
+2 -1
View File
@@ -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
+14 -13
View File
@@ -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
}
+3 -1
View File
@@ -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 {
@@ -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)
+29 -17
View File
@@ -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
}
+4 -1
View File
@@ -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)!
+7
View File
@@ -1,3 +1,10 @@
// vfmt off
fn test_stub() {
$if !new_veb ? {
return
}
}
$if new_veb ? {
import net
import net.http