From 4db897794ab2c23ff77edfa0d80d426f68e50dbf Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 24 Jun 2026 12:50:01 +0300 Subject: [PATCH] v3: fix self host --- vlib/v3/gen/c/cleanc.v | 20 ++++++++++- vlib/v3/gen/c/fn.v | 2 +- vlib/v3/gen/c/stmt.v | 72 +++++++++++++++++++++++++++++++++++---- vlib/v3/transform/array.v | 6 +++- vlib/v3/transform/fn.v | 49 +++++++++++++++++++++++--- vlib/v3/transform/map.v | 15 ++++++-- vlib/v3/types/checker.v | 22 ++++++++++++ 7 files changed, 169 insertions(+), 17 deletions(-) diff --git a/vlib/v3/gen/c/cleanc.v b/vlib/v3/gen/c/cleanc.v index 7ed4f3dc3..37dbc9902 100644 --- a/vlib/v3/gen/c/cleanc.v +++ b/vlib/v3/gen/c/cleanc.v @@ -2025,8 +2025,12 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) { g.gen_expr(g.a.child(&node, 1)) g.write('))') } else if base_type is types.String { + // Parenthesize the base: a smartcast sum variant yields a deref + // like `*v._string`, and `*v._string.str[i]` would bind as + // `*(v._string.str[i])`. `(*v._string).str[i]` is what we want. + g.write('(') g.gen_expr(base_id) - g.write('.str[') + g.write(').str[') g.gen_expr(g.a.child(&node, 1)) g.write(']') } else if base_type is types.Pointer { @@ -2693,6 +2697,13 @@ fn (mut g FlatGen) emit_global_inits() { if int(val_id) < 0 { continue } + // g_main_argc/g_main_argv are filled in by main's preamble (from argc/argv) + // *before* _vinit runs, and are zero by default in C anyway. Re-emitting their + // `= 0` initializer here would clobber the real argv, leaving os.args empty. + cqname := c_name(qname) + if cqname == 'g_main_argc' || cqname == 'g_main_argv' { + continue + } if typ := g.global_types[qname] { if typ is types.ArrayFixed { continue @@ -2831,6 +2842,13 @@ fn (mut g FlatGen) emit_const(name string, val_id flat.NodeId) { g.writeln('${c_elem} ${qname}${dims};') } else if ct != 'void' { g.writeln('${ct} ${qname};') + // The initializer is not a compile-time constant (e.g. `os.args = + // arguments()`), so it cannot be a C static initializer. Run it at startup + // in _vinit; otherwise the const stays zero/empty and first use is wrong. + g.runtime_inits << '\t${qname} = ${expr_str};' + if v_type is types.Map { + g.queue_map_literal_sets(qname, val_id, v_type) + } } g.tc.cur_module = old_module return diff --git a/vlib/v3/gen/c/fn.v b/vlib/v3/gen/c/fn.v index 1ff35e05e..afb86d26e 100644 --- a/vlib/v3/gen/c/fn.v +++ b/vlib/v3/gen/c/fn.v @@ -1734,7 +1734,7 @@ fn (g &FlatGen) is_missing_middleware_use_call(fn_node flat.Node) bool { if clean_type !is types.Struct { return false } - method_name := '${clean_type.name}.use' + method_name := '${clean_type.name()}.use' if method_name in g.tc.fn_param_types || method_name in g.tc.fn_ret_types { return false } diff --git a/vlib/v3/gen/c/stmt.v b/vlib/v3/gen/c/stmt.v index 97122213a..d3077fac4 100644 --- a/vlib/v3/gen/c/stmt.v +++ b/vlib/v3/gen/c/stmt.v @@ -253,9 +253,11 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) { raw_expr_type := g.tc.resolve_type(ret_id) expr_type := g.usable_expr_type(ret_id) call_ret_type := g.local_fn_call_return_type(ret_id, ret_node) + decl_ret_type := g.declared_call_return_type(ret_node) if g.optional_result_matches_base(raw_expr_type, base) || g.optional_result_matches_base(expr_type, base) - || g.optional_result_matches_base(call_ret_type, base) { + || g.optional_result_matches_base(call_ret_type, base) + || g.optional_result_matches_base(decl_ret_type, base) { g.write('return ') g.gen_expr(ret_id) g.writeln(';') @@ -495,9 +497,11 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no raw_expr_type := g.tc.resolve_type(ret_id) expr_type := g.usable_expr_type(ret_id) call_ret_type := g.local_fn_call_return_type(ret_id, ret_node) + decl_ret_type := g.declared_call_return_type(ret_node) if g.optional_result_matches_base(raw_expr_type, base) || g.optional_result_matches_base(expr_type, base) - || g.optional_result_matches_base(call_ret_type, base) { + || g.optional_result_matches_base(call_ret_type, base) + || g.optional_result_matches_base(decl_ret_type, base) { return g.expr_to_string(ret_id) } mut expr_value_type := expr_type @@ -592,6 +596,42 @@ fn (g &FlatGen) local_fn_call_return_type(call_id flat.NodeId, call_node flat.No return node_type } +// declared_call_return_type returns the *declared* return type of a (possibly +// lowered) call's target function, preserving type aliases. Method calls are +// lowered to ident calls (`Recv__method(recv, ...)`) before codegen, and the +// call node's own `.typ` annotation has aliases resolved away (e.g. `?NodeId` +// becomes `?int`), which makes the optional C type name appear to differ from +// the callee's signature. The declared type read from `fn_ret_types`/the fn decl +// keeps the alias, so propagating `return call()` is recognised as valid. +fn (g &FlatGen) declared_call_return_type(call_node flat.Node) types.Type { + if call_node.kind != .call || call_node.children_count == 0 { + return types.Type(types.void_) + } + fn_node := g.a.child_node(&call_node, 0) + if fn_node.kind == .selector { + if ret := g.selector_call_return_type(fn_node) { + return ret + } + return types.Type(types.void_) + } + if fn_node.kind != .ident { + return types.Type(types.void_) + } + if ret := g.tc.fn_ret_types[fn_node.value] { + return ret + } + cfn := c_name(fn_node.value) + if cfn != fn_node.value { + if ret := g.tc.fn_ret_types[cfn] { + return ret + } + } + if ret := g.fn_decl_return_type_for_call_name(fn_node.value) { + return ret + } + return types.Type(types.void_) +} + fn (g &FlatGen) selector_call_return_type(fn_node flat.Node) ?types.Type { if fn_node.children_count == 0 || fn_node.value.len == 0 { return none @@ -688,13 +728,31 @@ fn (g &FlatGen) expr_really_returns_optional(id flat.NodeId) bool { // optional_result_matches_base supports optional result matches base handling for FlatGen. fn (g &FlatGen) optional_result_matches_base(expr_type types.Type, base types.Type) bool { + mut inner := types.Type(types.void_) if expr_type is types.OptionType { - return g.type_names_match(expr_type.base_type, base) + inner = expr_type.base_type + } else if expr_type is types.ResultType { + inner = expr_type.base_type + } else { + return false + } + if g.type_names_match(inner, base) { + return true } - if expr_type is types.ResultType { - return g.type_names_match(expr_type.base_type, base) + // Aliases keep their declared name (e.g. `[]NodeId`) while `resolve_type` collapses + // them to the underlying type (`[]int`), so a structural name comparison spuriously + // fails. What actually matters for `return ;` is whether the C optional type + // emitted for the call equals the one this function returns — compare those instead. + return g.option_c_name_for_base(inner) == g.option_c_name_for_base(base) +} + +// option_c_name_for_base returns the C optional type name used for a `?base`/`!base` +// value, mirroring optional_type_name without its side effects. +fn (g &FlatGen) option_c_name_for_base(base types.Type) string { + if base is types.Void || base is types.Primitive || base is types.Enum { + return 'Optional' } - return false + return 'Optional_' + g.tc.c_type(base).replace('*', 'ptr').replace(' ', '_') } // usable_expr_type supports usable expr type handling for FlatGen. @@ -1184,7 +1242,7 @@ fn (g &FlatGen) assign_struct_operator_method(lhs_type types.Type, op flat.Op) ? return none } op_symbol := assign_struct_operator_symbol(op) or { return none } - method_name := '${clean.name}.${op_symbol}' + method_name := '${clean.name()}.${op_symbol}' if method_name in g.tc.fn_param_types || method_name in g.tc.fn_ret_types { return method_name } diff --git a/vlib/v3/transform/array.v b/vlib/v3/transform/array.v index 0909042f8..522181bc0 100644 --- a/vlib/v3/transform/array.v +++ b/vlib/v3/transform/array.v @@ -55,7 +55,11 @@ fn (mut t Transformer) lower_array_init_to_runtime(id flat.NodeId, node flat.Nod post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc)) elem_lhs := t.make_index(t.make_ident(tmp_name), t.make_ident(idx_name), elem_type) assign := t.make_assign(elem_lhs, init_expr) - t.pending_stmts << t.make_for_stmt(init_idx, cond, post, arr1(assign), node) + // `init:` expressions may reference the magic `index` variable, which V binds to + // the current element index. Declare it inside the loop body so it resolves to the + // generated loop counter instead of leaking to an external symbol (e.g. libc `index`). + index_decl := t.make_decl_assign_typed('index', t.make_ident(idx_name), 'int') + t.pending_stmts << t.make_for_stmt(init_idx, cond, post, arr2(index_decl, assign), node) return t.make_ident(tmp_name) } diff --git a/vlib/v3/transform/fn.v b/vlib/v3/transform/fn.v index 0e826beec..385afd452 100644 --- a/vlib/v3/transform/fn.v +++ b/vlib/v3/transform/fn.v @@ -473,6 +473,15 @@ fn (mut t Transformer) try_lower_join_path_call(id flat.NodeId, node flat.Node) if call_name != 'join_path' && call_name != 'os.join_path' { return none } + // A spread argument (`...rest`) has a runtime-determined length and cannot be + // unrolled into nested join_path_single calls at compile time. Defer to the real + // variadic os.join_path in that case. + for i in 1 .. node.children_count { + arg := t.a.child_node(&node, i) + if arg.kind == .prefix && arg.value == '...' { + return none + } + } if node.children_count <= 1 { return t.make_string_literal('') } @@ -571,6 +580,11 @@ fn (t &Transformer) call_param_offset(call_name string, node flat.Node, params [ return 0 } base_id := t.a.child(fn_node, 0) + // `module.Type.fn(...)` / `Type.fn(...)` is a static associated function call, not a + // method: the base names a type, not a value, so no receiver must be prepended. + if _ := t.static_assoc_fn_name(base_id, fn_node.value) { + return 0 + } if t.receiver_method_param_offset(base_id, node, params) == 1 { return 1 } @@ -584,6 +598,32 @@ fn (t &Transformer) call_param_offset(call_name string, node flat.Node, params [ return 0 } +// static_assoc_fn_name returns the name of the static associated function a selector +// call resolves to, if the base names a type (`Type.fn` or `module.Type.fn`) and that +// function exists. Such calls take no receiver, so the base must not be prepended as an +// argument. Returns none for ordinary method calls (base is a value). +fn (t &Transformer) static_assoc_fn_name(base_id flat.NodeId, method string) ?string { + if method.len == 0 { + return none + } + base := t.a.nodes[int(base_id)] + if base.kind == .ident { + name := '${base.value}.${method}' + if t.is_known_fn_name(name) { + return name + } + } else if base.kind == .selector && base.children_count > 0 { + inner := t.a.child_node(&base, 0) + if inner.kind == .ident { + name := '${inner.value}.${base.value}.${method}' + if t.is_known_fn_name(name) { + return name + } + } + } + return none +} + // call_param_types updates call param types state for Transformer. fn (t &Transformer) call_param_types(call_name string) []types.Type { if call_name.len == 0 || isnil(t.tc) { @@ -2084,11 +2124,10 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat. method := fn_node.value base_id := t.a.child(&fn_node, 0) base_node := t.a.nodes[int(base_id)] - if base_node.kind == .ident { - static_name := '${base_node.value}.${method}' - if t.is_known_fn_name(static_name) { - return none - } + // `Type.fn(...)` / `module.Type.fn(...)` is a static associated function, not a method: + // the base names a type, so it must not be lowered into `fn(receiver, ...)`. + if _ := t.static_assoc_fn_name(base_id, method) { + return none } mut base_type := if base_node.kind in [.selector, .index] { t.lvalue_type(base_id) diff --git a/vlib/v3/transform/map.v b/vlib/v3/transform/map.v index fc0d46a74..aa0e86a54 100644 --- a/vlib/v3/transform/map.v +++ b/vlib/v3/transform/map.v @@ -65,10 +65,21 @@ fn (mut t Transformer) map_index_info(index_id flat.NodeId) ?MapIndexInfo { if !map_type.starts_with('map[') { return none } - key_type, value_type := t.map_type_parts(map_type) - if key_type.len == 0 || value_type.len == 0 { + key_type, raw_value_type := t.map_type_parts(map_type) + if key_type.len == 0 || raw_value_type.len == 0 { return none } + // Qualify a bare local sum type (`Value` -> `eval.Value`). A bare name can clash + // with an imported type of the same name and resolve to the wrong sum type when the + // value is later wrapped into the map element type. + mut value_type := raw_value_type + if !value_type.contains('.') && !value_type.contains('[') && !value_type.starts_with('&') + && t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' { + qualified := '${t.cur_module}.${value_type}' + if qualified in t.sum_types || (!isnil(t.tc) && qualified in t.tc.sum_types) { + value_type = qualified + } + } return MapIndexInfo{ base_id: base_id key_id: key_id diff --git a/vlib/v3/types/checker.v b/vlib/v3/types/checker.v index 92a67d5ae..bf57931fe 100644 --- a/vlib/v3/types/checker.v +++ b/vlib/v3/types/checker.v @@ -1946,12 +1946,34 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) { tc.check_ident(id, node) return } + if node.kind == .array_init { + tc.check_array_init(node) + return + } for i in 0 .. node.children_count { tc.check_node(tc.a.child(&node, i)) } } +// check_array_init validates an `[]T{len: ..., init: ...}` initializer. The `init:` +// expression may reference the magic `index` variable (the current element index), +// so it is checked in a scope where `index` is bound to an int. +fn (mut tc TypeChecker) check_array_init(node flat.Node) { + for i in 0 .. node.children_count { + child_id := tc.a.child(&node, i) + child := tc.a.nodes[int(child_id)] + if child.kind == .field_init && child.value == 'init' { + tc.push_scope() + tc.cur_scope.insert('index', Type(int_)) + tc.check_node(child_id) + tc.pop_scope() + } else { + tc.check_node(child_id) + } + } +} + // check_or_expr validates check or expr state for types. fn (mut tc TypeChecker) check_or_expr(node flat.Node) { if node.children_count == 0 { -- 2.39.5