From 66207866bb98f11345ce49c67014dde5b4a65cdb Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 18 Apr 2026 14:36:49 +0300 Subject: [PATCH] all: more fixes --- vlib/strings/builder_test.v | 17 +++-- vlib/v/checker/checker.v | 22 +++--- vlib/v/checker/infix.v | 6 +- vlib/v/gen/c/cgen.v | 44 ++++++++++-- vlib/v/gen/c/comptime.v | 7 +- vlib/v/gen/c/infix.v | 2 +- vlib/v/gen/c/str.v | 9 +++ vlib/v/gen/c/str_intp.v | 72 +++++-------------- vlib/v/gen/c/struct.v | 12 +++- vlib/v/gen/c/utils.v | 37 +++++++++- vlib/v/parser/parser.v | 2 +- vlib/v/tests/comptime/comptime_call_test.v | 2 +- .../comptime/comptime_var_assignment_test.v | 2 +- 13 files changed, 145 insertions(+), 89 deletions(-) diff --git a/vlib/strings/builder_test.v b/vlib/strings/builder_test.v index 48101e4b1..c55a6e26c 100644 --- a/vlib/strings/builder_test.v +++ b/vlib/strings/builder_test.v @@ -119,15 +119,17 @@ fn test_ensure_cap() { mut sb := strings.new_builder(0) assert sb.cap == 0 sb.ensure_cap(10) - assert sb.cap == 10 + assert sb.cap >= 10 + old_cap := sb.cap sb.ensure_cap(10) - assert sb.cap == 10 + assert sb.cap == old_cap sb.ensure_cap(15) - assert sb.cap == 15 + assert sb.cap >= 15 + old_cap2 := sb.cap sb.ensure_cap(10) - assert sb.cap == 15 + assert sb.cap == old_cap2 sb.ensure_cap(-1) - assert sb.cap == 15 + assert sb.cap == old_cap2 } fn test_drain_builder() { @@ -186,11 +188,12 @@ fn test_grow_len() { sb.ensure_cap(35) assert sb.len == 20 - assert sb.cap == 35 + assert sb.cap >= 35 + cap_after_ensure := sb.cap unsafe { sb.grow_len(5) } assert sb.len == 25 - assert sb.cap == 35 + assert sb.cap == cap_after_ensure } fn test_write_repeated_rune() { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 30079a5e4..363a67058 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3680,16 +3680,18 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { replacement_expr = comptime_lit } } - folded_expr := - c.checker_transformer.expr(mut field.expr.obj.expr) - - if folded_expr is ast.IntegerLiteral { - c.check_enum_field_integer_literal(folded_expr, signed, - node.is_multi_allowed, senum_type, field.expr.pos, mut - useen, enum_umin, enum_umax, mut iseen, enum_imin, - enum_imax) - has_replacement = true - replacement_expr = folded_expr + if !has_replacement { + folded_expr := + c.checker_transformer.expr(mut field.expr.obj.expr) + + if folded_expr is ast.IntegerLiteral { + c.check_enum_field_integer_literal(folded_expr, signed, + node.is_multi_allowed, senum_type, field.expr.pos, mut + useen, enum_umin, enum_umax, mut iseen, enum_imin, + enum_imax) + has_replacement = true + replacement_expr = folded_expr + } } } } diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index fc921a62c..c37d3c24f 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -860,7 +860,11 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { right_expr := node.right mut typ := match right_expr { ast.TypeNode { - right_expr.typ + if right_expr.typ.has_flag(.generic) { + c.unwrap_generic(right_expr.typ) + } else { + right_expr.typ + } } ast.None { ast.none_type_idx diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 250cfa8b6..007deda9b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3042,7 +3042,15 @@ fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) bool { g.expr(stmt.expr) g.writeln(';') } else { - ret_typ := g.fn_decl.return_type.clear_flag(.result) + // on assignment or struct field initialization + inside_assign_context := g.inside_struct_init + || g.inside_assign + || (!g.inside_return && g.inside_match_result) + ret_typ := if inside_assign_context { + stmt.typ + } else { + g.fn_decl.return_type.clear_flag(.result) + } styp = g.base_type(ret_typ) if stmt.expr is ast.CallExpr && stmt.expr.is_noreturn { g.expr(ast.Expr(stmt.expr)) @@ -6180,12 +6188,23 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { } mut selector_expr_expr := node.expr mut is_sumtype_smartcast_expr_ptr := false + // Track if the ident's original type is a sumtype/interface and it has + // smartcasts; the ident codegen dereferences the variant pointer with `*`, + // so the result is a value even if the original type is a pointer. + mut smartcast_ident_already_dereferenced := false if mut selector_expr_expr is ast.Ident && selector_scope != unsafe { nil } { selector_expr_expr.scope = selector_scope if scope_var := selector_scope.find_var(selector_expr_expr.name) { if scope_var.smartcasts.len > 0 { selector_expr_expr.obj = *scope_var is_sumtype_smartcast_expr_ptr = g.table.final_sym(g.unwrap_generic(scope_var.typ)).kind == .sum_type + orig_sym := g.table.final_sym(g.unwrap_generic(scope_var.orig_type)) + // For sum types: ident codegen emits `*name->_variant` which + // dereferences the variant pointer, producing a value. + // For interfaces: ident codegen emits `*(type*)name._variant` + // which also dereferences the interface field pointer. + // In both cases the expression result is a value, not a ptr. + smartcast_ident_already_dereferenced = orig_sym.kind in [.sum_type, .interface] } } } @@ -6683,19 +6702,27 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { } interface_smartcast_expr_is_dereferenced := is_interface_smartcast_expr && smartcast_expr_var.smartcasts.last().nr_muls() == exposed_interface_smartcast_type.nr_muls() + 1 + // An interface smartcast to a non-interface type normally produces a pointer + // (the interface stores fields as pointers). But the ident codegen may + // already dereference the pointer, depending on the exposed smartcast type. + // If exposed_interface_smartcast_type has the same nr_muls as the raw + // smartcast type, the pointer is NOT exposed (ident adds `*` dereference). interface_smartcast_selector_emits_ptr := is_interface_smartcast_selector || (is_interface_smartcast_expr - && g.table.final_sym(g.unwrap_generic(smartcast_expr_var.smartcasts.last())).kind != .interface) + && g.table.final_sym(g.unwrap_generic(smartcast_expr_var.smartcasts.last())).kind != .interface + && exposed_interface_smartcast_type.is_ptr()) left_is_ptr := if expr_is_unwrapped_autoheap_option { false } else { field_is_opt || expr_is_auto_heap || interface_smartcast_selector_emits_ptr - || (is_interface_smartcast_lhs && !interface_smartcast_expr_is_dereferenced) - || (((!is_dereferenced && !is_interface_smartcast_lhs && unwrapped_expr_type.is_ptr()) - || sym.kind == .chan || alias_to_ptr) && node.from_embed_types.len == 0) + || (is_interface_smartcast_lhs && !interface_smartcast_expr_is_dereferenced + && !smartcast_ident_already_dereferenced) || (((!is_dereferenced + && !is_interface_smartcast_lhs && !smartcast_ident_already_dereferenced + && unwrapped_expr_type.is_ptr()) || sym.kind == .chan || alias_to_ptr) + && node.from_embed_types.len == 0) || (node.expr.is_as_cast() && g.inside_smartcast) || (!opt_ptr_already_deref && unwrapped_expr_type.is_ptr() - && !is_interface_smartcast_lhs) + && !is_interface_smartcast_lhs && !smartcast_ident_already_dereferenced) } if !has_embed && left_is_ptr { g.write('->') @@ -10228,6 +10255,11 @@ fn (mut g Gen) gen_or_block_stmts(cvar_name string, cast_typ string, stmts []ast && call_expr.or_block.kind == .absent { g.write('${cvar_name} = ') return_wrapped = true + } else if call_expr.or_block.kind != .absent { + // Call with ? or ! - the call codegen will + // unwrap the option/result, so assign the + // unwrapped value to the or-block's data slot. + g.write('*(${cast_typ}*) ${cvar_name}${tmp_op}data = ') } } else if expr_stmt.expr is ast.CallExpr { call_expr := expr_stmt.expr as ast.CallExpr diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 79a210784..1c6a84031 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -218,11 +218,12 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { g.writeln('builtin__string_free(&_tmpl_res_${fn_name});') } else { // return $tmpl string - g.write(cur_line) if g.inside_return_tmpl { - g.write('return ') + g.writeln('return _tmpl_res_${fn_name};') + } else { + g.write(cur_line) + g.write('_tmpl_res_${fn_name}') } - g.write('_tmpl_res_${fn_name}') } return } diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 658be3d4e..3d1ab2485 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -1100,7 +1100,7 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { node.left_type))) is_aggregate := node.left is ast.Ident && g.comptime.get_ct_type_var(node.left) == .aggregate right_sym := g.table.sym(node.right_type) - mut right_type := node.right_type + mut right_type := g.unwrap_generic(node.right_type) // When the LHS is a smartcast variable whose original type is a sum type, // use the original sum type so the `is` check works on the tag field. // But only when the smartcast target is NOT itself a sum type — for nested diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index 18cba6412..22cd724ac 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -76,6 +76,13 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } } sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + // When interface smartcast expr produces a pointer in C but type was already dereffed, + // we need to dereference the generated expression. + is_interface_smartcast_to_nonptr := !is_ptr && expr is ast.Ident && expr.obj is ast.Var + && (expr.obj as ast.Var).smartcasts.len > 0 + && (expr.obj as ast.Var).smartcasts.last().is_ptr() + && g.table.final_sym(g.unwrap_generic((expr.obj as ast.Var).orig_type)).kind == .interface + && g.table.final_sym(g.unwrap_generic((expr.obj as ast.Var).smartcasts.last())).kind != .interface use_raw_interface_smartcast_expr := is_ptr && expr is ast.Ident && expr.obj is ast.Var && (expr.obj as ast.Var).smartcasts.len > 0 && (expr.obj as ast.Var).smartcasts.last().is_ptr() @@ -208,6 +215,8 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } else { g.write('*'.repeat(etype.nr_muls())) } + } else if !str_method_expects_ptr && is_interface_smartcast_to_nonptr { + g.write('*') } else if sym.is_c_struct() { g.write(c_struct_ptr(sym, typ, str_method_expects_ptr)) } diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 3e8f96daf..9ecf8d9dc 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -12,52 +12,6 @@ module c import v.ast import v.util -// When a smartcast variable's original type is a sumtype with an explicit -// str() method, generate a call to the sumtype's str() with the original -// un-smartcasted variable, bypassing the variant's auto-generated str(). -// Returns true if handled, false if the caller should use the normal path. -fn (mut g Gen) gen_sumtype_str_for_smartcast(expr ast.Expr) bool { - if expr is ast.Ident && expr.obj is ast.Var && expr.obj.smartcasts.len > 0 { - orig := g.unwrap_generic(expr.obj.typ) - orig_sym := g.table.sym(orig) - if orig_sym.kind == .sum_type && orig_sym.has_method('str') { - str_fn_name := g.get_str_fn(orig) - _, str_method_expects_ptr, _ := orig_sym.str_method_info() - g.write2(str_fn_name, '(') - if expr.obj.is_auto_deref || orig.is_ptr() || expr.obj.is_mut { - if str_method_expects_ptr { - // pointer variable, str() expects pointer - pass as-is - } else { - g.write('*') - } - } - g.write(c_name(expr.name)) - g.write(')') - return true - } - } else if expr is ast.SelectorExpr { - scope := g.file.scope.innermost(expr.pos.pos) - field := scope.find_struct_field(expr.expr.str(), expr.expr_type, expr.field_name) - if field != unsafe { nil } && field.smartcasts.len > 0 { - orig := g.unwrap_generic(field.orig_type) - orig_sym := g.table.sym(orig) - if orig_sym.kind == .sum_type && orig_sym.has_method('str') { - str_fn_name := g.get_str_fn(orig) - _, str_method_expects_ptr, _ := orig_sym.str_method_info() - g.write2(str_fn_name, '(') - if !str_method_expects_ptr && orig.is_ptr() { - g.write('*') - } - g.prevent_sum_type_unwrapping_once = true - g.expr(expr) - g.write(')') - return true - } - } - } - return false -} - fn (mut g Gen) is_type_name_string_expr(expr ast.Expr) bool { return match expr { ast.SelectorExpr { @@ -484,9 +438,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { } } } - if !is_comptime_for_var && g.gen_sumtype_str_for_smartcast(expr) { - // handled - } else if exp_typ.has_flag(.option) && expr is ast.Ident && g.is_comptime_for_var(expr) { + if exp_typ.has_flag(.option) && expr is ast.Ident && g.is_comptime_for_var(expr) { str_fn_name := g.get_str_fn(exp_typ.clear_flag(.option)) g.write('${str_fn_name}(*(${g.base_type(exp_typ)}*)(') old_inside_opt_or_res := g.inside_opt_or_res @@ -778,6 +730,20 @@ fn (mut g Gen) gen_simple_string_inter_literal(node ast.StringInterLiteral, fmts || node.expr_types[i].is_float_valptr() { return false } + // Interface types need the full str_intp path for vtable dispatch and + // interface smartcasts need it for correct pointer prefix handling. + expr_i := node.exprs[i] + if expr_i is ast.Ident && expr_i.obj is ast.Var { + expr_var := expr_i.obj as ast.Var + if expr_var.orig_type != 0 + && g.table.final_sym(g.unwrap_generic(expr_var.orig_type)).kind == .interface { + return false + } + } + etyp_sym := g.table.final_sym(node.expr_types[i]) + if etyp_sym.kind == .interface { + return false + } if i < node.fwidths.len && node.fwidths[i] != 0 { return false } @@ -799,9 +765,7 @@ fn (mut g Gen) gen_simple_string_inter_literal(node ast.StringInterLiteral, fmts } } if node.exprs.len == 1 && node.vals.len == 2 && node.vals[0].len == 0 && node.vals[1].len == 0 { - if !g.gen_sumtype_str_for_smartcast(node.exprs[0]) { - g.gen_expr_to_string(node.exprs[0], node.expr_types[0]) - } + g.gen_expr_to_string(node.exprs[0], node.expr_types[0]) return true } mut part_count := 0 @@ -833,9 +797,7 @@ fn (mut g Gen) gen_simple_string_inter_literal(node ast.StringInterLiteral, fmts if written_parts > 0 { g.write(', ') } - if !g.gen_sumtype_str_for_smartcast(node.exprs[i]) { - g.gen_expr_to_string(node.exprs[i], node.expr_types[i]) - } + g.gen_expr_to_string(node.exprs[i], node.expr_types[i]) written_parts++ } } diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 14b8a902c..fdab6c8e0 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -93,7 +93,7 @@ fn (mut g Gen) struct_init(node ast.StructInit) { } is_anon = sym.info.is_anon } - is_generic_default := sym.kind !in [.struct, .array_fixed, .generic_inst] + mut is_generic_default := sym.kind !in [.struct, .array_fixed, .generic_inst] && base_node_typ.has_flag(.generic) // T{} is_array := sym.kind in [.array_fixed, .array] if sym.kind == .array_fixed { @@ -157,6 +157,16 @@ fn (mut g Gen) struct_init(node ast.StructInit) { pointee_type := resolved_ptr_type.set_nr_muls(0) basetyp := g.styp(pointee_type) pointee_sym := g.table.final_sym(pointee_type) + // For primitive pointer types (e.g. T{} where T = &int), the default + // value is a null pointer, not a compound literal address. + if pointee_sym.kind !in [.struct, .array_fixed, .array, .sum_type, .interface, .map] + && !pointee_sym.is_heap() && node.init_fields.len == 0 { + g.write('0') + return + } + // We're generating a compound literal address `&(type){...}`, + // so is_generic_default must not suppress the content and closing brace. + is_generic_default = false if pointee_sym.is_heap() { is_ptr_heap_init = true if aligned != 0 { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index fe019a94a..dee20c4b0 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -121,7 +121,23 @@ fn (mut g Gen) infer_branch_expr_type(stmts []ast.Stmt) ast.Type { fn (mut g Gen) infer_if_expr_type(node ast.IfExpr) ast.Type { if node.typ != 0 && node.typ != ast.void_type { - return g.unwrap_generic(g.recheck_concrete_type(node.typ)) + resolved := g.unwrap_generic(g.recheck_concrete_type(node.typ)) + // In generic functions, node.typ may have been mutated by the checker + // to a concrete type from the last processed instantiation. When the + // if-expr is used as a return value (g.inside_return), use the function's + // return type instead, which correctly resolves via cur_concrete_types. + if g.inside_return && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { + fn_ret := g.unwrap_generic(g.recheck_concrete_type(g.cur_fn.return_type)) + if fn_ret != 0 && fn_ret != ast.void_type { + if node.typ.has_flag(.result) && !fn_ret.has_flag(.result) { + return fn_ret.set_flag(.result) + } else if node.typ.has_flag(.option) && !fn_ret.has_flag(.option) { + return fn_ret.set_flag(.option) + } + return fn_ret + } + } + return resolved } for branch in node.branches { branch_typ := g.infer_branch_expr_type(branch.stmts) @@ -134,7 +150,24 @@ fn (mut g Gen) infer_if_expr_type(node ast.IfExpr) ast.Type { fn (mut g Gen) infer_match_expr_type(node ast.MatchExpr) ast.Type { if node.return_type != 0 && node.return_type != ast.void_type { - return g.unwrap_generic(g.recheck_concrete_type(node.return_type)) + resolved := g.unwrap_generic(g.recheck_concrete_type(node.return_type)) + // In generic functions, node.return_type may have been mutated by the checker + // to a concrete type from the last processed instantiation. When the match is + // used as a return value (g.inside_return), use the function's return type + // instead, which correctly resolves via cur_concrete_types. + if g.inside_return && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { + fn_ret := g.unwrap_generic(g.recheck_concrete_type(g.cur_fn.return_type)) + if fn_ret != 0 && fn_ret != ast.void_type { + // Preserve option/result flags from the match's return_type + if node.return_type.has_flag(.result) && !fn_ret.has_flag(.result) { + return fn_ret.set_flag(.result) + } else if node.return_type.has_flag(.option) && !fn_ret.has_flag(.option) { + return fn_ret.set_flag(.option) + } + return fn_ret + } + } + return resolved } for branch in node.branches { branch_typ := g.infer_branch_expr_type(branch.stmts) diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 92ddb8be3..88294825f 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1219,7 +1219,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { // dump(extra) extra_pos = extra_pos.extend(p.tok.pos()) } else if p.tok.line_nr == p.prev_tok.line_nr + p.prev_tok.lit.count('\n') - && p.tok.kind !in [.comment, .semicolon, .rcbr, .eof] { + && p.tok.kind !in [.comment, .semicolon, .rcbr, .eof, .key_return, .key_break, .key_continue] { line_nr := p.tok.line_nr err := p.unexpected(got: p.tok.str(), expecting: '`,`') for p.tok.kind != .eof && p.tok.line_nr == line_nr { diff --git a/vlib/v/tests/comptime/comptime_call_test.v b/vlib/v/tests/comptime/comptime_call_test.v index 8dc85327c..4c737368b 100644 --- a/vlib/v/tests/comptime/comptime_call_test.v +++ b/vlib/v/tests/comptime/comptime_call_test.v @@ -68,7 +68,7 @@ fn test_methods_arg() { s1 := S1{} $for method in S1.methods { arr := ['!', '3'] - r := s1.$method(arr) + r := s1.$method(...arr) assert r == '!!!' } } diff --git a/vlib/v/tests/comptime/comptime_var_assignment_test.v b/vlib/v/tests/comptime/comptime_var_assignment_test.v index 62db3d8ed..42404f269 100644 --- a/vlib/v/tests/comptime/comptime_var_assignment_test.v +++ b/vlib/v/tests/comptime/comptime_var_assignment_test.v @@ -12,7 +12,7 @@ fn encode_struct[T](val T) []string { $if field.is_option { gg := value ? println(gg) - out << '${value}' + out << '${gg}' } $else { gg := value println(gg) -- 2.39.5