From 9335a84eb35cbd58c71a559b397b38fd57697ea6 Mon Sep 17 00:00:00 2001 From: Swastik Baranwal Date: Wed, 28 Jan 2026 01:30:10 +0530 Subject: [PATCH] parser, checker: check generic struct fields and initialisation (fix #26433) (fix #26436) (#26450) --- vlib/builtin/js/promise.js.v | 2 +- vlib/datatypes/doubly_linked_list.v | 3 ++- vlib/v/checker/checker.v | 8 +++++--- vlib/v/checker/fn.v | 10 +++++----- vlib/v/checker/struct.v | 4 ++-- vlib/v/checker/tests/amod/amod.v | 4 ++++ .../tests/modules/module_struct_noinit.out | 16 +++++++++++----- .../modules/module_struct_noinit/src/main.v | 3 +++ .../tests/modules/module_struct_noinit/src/mod.v | 4 ++++ .../v/checker/tests/struct_field_private_err.out | 13 ++++++++++--- vlib/v/checker/tests/struct_field_private_err.vv | 4 ++++ vlib/v/parser/fn.v | 2 +- vlib/v/parser/parse_type.v | 5 ++++- .../generics/modules/simplemodule/simplemodule.v | 1 + vlib/x/sessions/memory_store.v | 2 +- 15 files changed, 58 insertions(+), 23 deletions(-) diff --git a/vlib/builtin/js/promise.js.v b/vlib/builtin/js/promise.js.v index 0d6cdc807..89a3f9656 100644 --- a/vlib/builtin/js/promise.js.v +++ b/vlib/builtin/js/promise.js.v @@ -14,7 +14,7 @@ pub fn JS.Promise.race(JS.Array) JS.Promise // Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value. pub struct Promise[T] { -mut: +pub mut: promise JS.Promise @[noinit] } diff --git a/vlib/datatypes/doubly_linked_list.v b/vlib/datatypes/doubly_linked_list.v index 70e5a413e..d7f068829 100644 --- a/vlib/datatypes/doubly_linked_list.v +++ b/vlib/datatypes/doubly_linked_list.v @@ -21,7 +21,8 @@ mut: // of the list while iterating. TODO: use an option // instead of a pointer to determine it is initialized. iter &DoublyListIter[T] = unsafe { 0 } - len int +pub mut: + len int } // is_empty checks if the linked list is empty diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 02d7e556d..e3116b665 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -138,6 +138,7 @@ mut: inside_decl_rhs bool inside_if_guard bool // true inside the guard condition of `if x := opt() {}` inside_assign bool + is_js_backend bool // doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect) // doing_line_path string // same, but stores the path being parsed is_index_assign bool @@ -185,6 +186,7 @@ pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker { checker.checker_transformer.skip_array_transform = true checker.type_resolver = type_resolver.TypeResolver.new(table, checker) checker.comptime = &checker.type_resolver.info + checker.is_js_backend = checker.pref.backend.is_js() return checker } @@ -2815,7 +2817,7 @@ fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) { c.warn('inline assembly goto is not supported, it will most likely not work', stmt.pos) } - if c.pref.backend.is_js() { + if c.is_js_backend { c.error('inline assembly is not supported in the js backend', stmt.pos) } mut aliases := c.asm_ios(mut stmt.output, mut stmt.scope, true) @@ -2914,7 +2916,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { if c.ct_cond_stack.len > 0 { node.ct_conds = c.ct_cond_stack.clone() } - if c.pref.backend.is_js() || c.pref.backend == .golang { + if c.pref.backend == .golang || c.is_js_backend { // consider the best way to handle the .go.vv files if !c.file.path.ends_with('.js.v') && !c.file.path.ends_with('.go.v') && !c.file.path.ends_with('.go.vv') { @@ -3729,7 +3731,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } c.check_any_type(to_type, to_sym, node.pos) - if c.pref.backend.is_js() { + if c.is_js_backend { if (to_sym.is_number() && from_sym.name == 'JS.Number') || (to_sym.is_number() && from_sym.name == 'JS.BigInt') || (to_sym.is_string() && from_sym.name == 'JS.String') diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 2da62c894..9334e5ba1 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1536,7 +1536,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } } c.set_node_expected_arg_types(mut node, func) - if !c.pref.backend.is_js() && args_len > 0 && func.params.len == 0 { + if !c.is_js_backend && args_len > 0 && func.params.len == 0 { c.error('too many arguments in call to `${func.name}` (non-js backend: ${c.pref.backend})', node.pos) } @@ -1925,7 +1925,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. && arg_typ !in [ast.voidptr_type, ast.nil_type] && arg_typ.nr_muls() == 0 && func.name !in ['isnil', 'ptr_str'] && !func.name.starts_with('json.') && arg_typ_sym.kind !in [.float_literal, .int_literal, .charptr, .function] - && !c.pref.backend.is_js() { + && !c.is_js_backend { c.warn('automatic ${arg_typ_sym.name} referencing/dereferencing into voidptr is deprecated and will be removed soon; use `foo(&x)` instead of `foo(x)`', call_arg.pos) } @@ -2209,7 +2209,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) && !(left_sym.kind == .alias && left_sym.has_method(method_name)) { unaliased_left_type := c.table.unaliased_type(left_type) return c.map_builtin_method_call(mut node, unaliased_left_type) - } else if c.pref.backend.is_js() && left_sym.name.starts_with('Promise[') && node.kind == .wait { + } else if c.is_js_backend && left_sym.name.starts_with('Promise[') && node.kind == .wait { info := left_sym.info as ast.Struct if node.args.len > 0 { c.error('wait() does not have any arguments', node.args[0].pos) @@ -2903,7 +2903,7 @@ fn (mut c Checker) spawn_expr(mut node ast.SpawnExpr) ast.Type { node.call_expr.left.pos()) } - if c.pref.backend.is_js() { + if c.is_js_backend { return c.table.find_or_register_promise(c.unwrap_generic(ret_type)) } else { return c.table.find_or_register_thread(c.unwrap_generic(ret_type)) @@ -2930,7 +2930,7 @@ fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { node.call_expr.left.pos()) } - if c.pref.backend.is_js() { + if c.is_js_backend { return c.table.find_or_register_promise(c.unwrap_generic(ret_type)) } else { return c.table.find_or_register_thread(c.unwrap_generic(ret_type)) diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index 0763042fc..745884a5b 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -1065,7 +1065,6 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', fn (mut c Checker) check_uninitialized_struct_fields_and_embeds(node ast.StructInit, type_sym ast.TypeSymbol, mut info ast.Struct, mut inited_fields []string) { mut fields := c.table.struct_fields(type_sym) mut checked_types := []ast.Type{} - for i, mut field in fields { if field.name in inited_fields { if c.mod != type_sym.mod { @@ -1078,7 +1077,8 @@ fn (mut c Checker) check_uninitialized_struct_fields_and_embeds(node ast.StructI } else { parts.last() } - if !c.inside_unsafe { + if !c.inside_unsafe && !(c.is_js_backend + && mod_type.starts_with('Promise')) { c.error('cannot access private field `${field.name}` on `${mod_type}`', init_field.pos) diff --git a/vlib/v/checker/tests/amod/amod.v b/vlib/v/checker/tests/amod/amod.v index e67d2e948..eb22830ef 100644 --- a/vlib/v/checker/tests/amod/amod.v +++ b/vlib/v/checker/tests/amod/amod.v @@ -9,6 +9,10 @@ pub struct Bcg { x int } +pub struct Bcg2[T] { + x int +} + @[params] pub struct FooParams { bar string diff --git a/vlib/v/checker/tests/modules/module_struct_noinit.out b/vlib/v/checker/tests/modules/module_struct_noinit.out index 7d9d36599..2c5a8f15b 100644 --- a/vlib/v/checker/tests/modules/module_struct_noinit.out +++ b/vlib/v/checker/tests/modules/module_struct_noinit.out @@ -1,6 +1,12 @@ -vlib/v/checker/tests/modules/module_struct_noinit/src/main.v:9:9: error: struct `mod.Foo` is declared with a `@[noinit]` attribute, so it cannot be initialized with `mod.Foo{}` - 7 | - 8 | fn default_value[T]() T { - 9 | return T{} +vlib/v/checker/tests/modules/module_struct_noinit/src/main.v:12:9: error: struct `mod.Foo` is declared with a `@[noinit]` attribute, so it cannot be initialized with `mod.Foo{}` + 10 | + 11 | fn default_value[T]() T { + 12 | return T{} | ~~~ - 10 | } + 13 | } +vlib/v/checker/tests/modules/module_struct_noinit/src/main.v:12:9: error: struct `mod.Foo2[int]` is declared with a `@[noinit]` attribute, so it cannot be initialized with `mod.Foo2[int]{}` + 10 | + 11 | fn default_value[T]() T { + 12 | return T{} + | ~~~ + 13 | } diff --git a/vlib/v/checker/tests/modules/module_struct_noinit/src/main.v b/vlib/v/checker/tests/modules/module_struct_noinit/src/main.v index 032fb44c3..7ee572f9d 100644 --- a/vlib/v/checker/tests/modules/module_struct_noinit/src/main.v +++ b/vlib/v/checker/tests/modules/module_struct_noinit/src/main.v @@ -3,6 +3,9 @@ import mod fn main() { dump(default_value[mod.Foo]()) println(default_value[mod.Foo]()) + + dump(default_value[mod.Foo2[int]]()) + println(default_value[mod.Foo2[int]]()) } fn default_value[T]() T { diff --git a/vlib/v/checker/tests/modules/module_struct_noinit/src/mod.v b/vlib/v/checker/tests/modules/module_struct_noinit/src/mod.v index 0db32d06f..d6afd0f1f 100644 --- a/vlib/v/checker/tests/modules/module_struct_noinit/src/mod.v +++ b/vlib/v/checker/tests/modules/module_struct_noinit/src/mod.v @@ -3,3 +3,7 @@ module mod @[noinit] pub struct Foo { } + +@[noinit] +pub struct Foo2[T] { +} diff --git a/vlib/v/checker/tests/struct_field_private_err.out b/vlib/v/checker/tests/struct_field_private_err.out index 286a91df2..c2a1420dd 100644 --- a/vlib/v/checker/tests/struct_field_private_err.out +++ b/vlib/v/checker/tests/struct_field_private_err.out @@ -5,8 +5,15 @@ vlib/v/checker/tests/struct_field_private_err.vv:8:2: error: cannot access priva | ~~~~ 9 | } 10 | -vlib/v/checker/tests/struct_field_private_err.vv:11:10: error: cannot access private field `bar` on `amod.FooParams` - 9 | } +vlib/v/checker/tests/struct_field_private_err.vv:12:2: error: cannot access private field `x` on `amod.Bcg2[int]` 10 | - 11 | amod.foo(bar: 'bar') + 11 | _ := amod.Bcg2[int]{ + 12 | x: 0 + | ~~~~ + 13 | } + 14 | +vlib/v/checker/tests/struct_field_private_err.vv:15:10: error: cannot access private field `bar` on `amod.FooParams` + 13 | } + 14 | + 15 | amod.foo(bar: 'bar') | ~~~~~~~~~~ diff --git a/vlib/v/checker/tests/struct_field_private_err.vv b/vlib/v/checker/tests/struct_field_private_err.vv index d1a8f91c1..f03d714f7 100644 --- a/vlib/v/checker/tests/struct_field_private_err.vv +++ b/vlib/v/checker/tests/struct_field_private_err.vv @@ -8,4 +8,8 @@ _ := amod.Bcg{ x: 0 } +_ := amod.Bcg2[int]{ + x: 0 +} + amod.foo(bar: 'bar') diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 9216f6287..966f77329 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -842,7 +842,7 @@ run them via `v file.v` instead', is_non_local = elem_type_sym.mod.len > 0 && elem_type_sym.mod != p.mod && elem_type_sym.language == .v } - if is_non_local { + if is_non_local && !(p.file_backend_mode == .js && type_sym.mod.starts_with('Promise')) { p.error_with_pos('cannot define new methods on non-local type ${type_sym.name}. Define an alias and use that instead like `type AliasName = ${type_sym.name}` ', rec.type_pos) return ast.FnDecl{ diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index e24893501..21313b42d 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -1026,12 +1026,15 @@ fn (mut p Parser) parse_generic_inst_type(name string, name_pos token.Pos) ast.T } } } + // mod.Foo[int] -> mod + // mod.submod.Foo[int] -> mod.submod + mod := name.all_before_last('.') idx := p.table.register_sym(ast.TypeSymbol{ kind: .generic_inst name: bs_name cname: util.no_dots(bs_cname) - mod: p.mod + mod: mod info: ast.GenericInst{ parent_idx: parent_idx concrete_types: concrete_types diff --git a/vlib/v/tests/generics/modules/simplemodule/simplemodule.v b/vlib/v/tests/generics/modules/simplemodule/simplemodule.v index 3b3ced78d..debaf239c 100644 --- a/vlib/v/tests/generics/modules/simplemodule/simplemodule.v +++ b/vlib/v/tests/generics/modules/simplemodule/simplemodule.v @@ -9,6 +9,7 @@ pub fn imul(x int, y int) int { } pub struct ThisIsGeneric[T] { +pub: msg T } diff --git a/vlib/x/sessions/memory_store.v b/vlib/x/sessions/memory_store.v index e868b3de4..25b80fe94 100644 --- a/vlib/x/sessions/memory_store.v +++ b/vlib/x/sessions/memory_store.v @@ -10,7 +10,7 @@ pub mut: // MemoryStore stores sessions in a `map` in memory only. pub struct MemoryStore[T] { -mut: +pub mut: data map[string]MemoryStoreSessions[T] } -- 2.39.5