From 95e1b7c14c92bf08a7655b63c7647eb6bd5d811a Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Tue, 16 Sep 2025 09:01:31 -0300 Subject: [PATCH] cgen,checker: fix option generic passing to `T` param (fix #25267) (fix #24466) (partial fix for #25227) (#25292) --- vlib/v/ast/table.v | 12 +++++- vlib/v/checker/assign.v | 3 ++ vlib/v/checker/postfix.v | 20 ++++++++-- vlib/v/gen/c/assign.v | 12 ++++++ vlib/v/gen/c/cgen.v | 20 ++++++++-- vlib/v/gen/c/fn.v | 15 +++++--- vlib/v/gen/c/struct.v | 4 +- .../tests/options/option_generic_array_test.v | 38 +++++++++++++++++++ .../tests/options/option_generic_param_test.v | 14 +++++++ vlib/v/type_resolver/generic_resolver.v | 3 ++ vlib/x/json2/encode.v | 12 ++++-- 11 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 vlib/v/tests/options/option_generic_array_test.v create mode 100644 vlib/v/tests/options/option_generic_param_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 9cef439a4..f908d1710 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1733,11 +1733,19 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str if typ == 0 { return none } + mut rtyp := typ.derive_add_muls(generic_type) if typ.has_flag(.generic) { - return typ.derive_add_muls(generic_type).set_flag(.generic) + rtyp = rtyp.set_flag(.generic) } else { - return typ.derive_add_muls(generic_type).clear_flag(.generic) + rtyp = rtyp.clear_flag(.generic) } + if !generic_type.has_flag(.result) && typ.has_flag(.option) { + rtyp = rtyp.set_flag(.option) + if generic_type.is_ptr() { + rtyp = rtyp.set_flag(.option_mut_param_t) + } + } + return rtyp } match mut sym.info { Array { diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index b53703e6b..8596dd183 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -1002,6 +1002,9 @@ fn (mut c Checker) change_flags_if_comptime_expr(mut left ast.Ident, right ast.E if ctyp != ast.void_type { left.obj.ct_type_var = right_obj_var.ct_type_var left.obj.typ = ctyp + if ctyp.has_flag(.generic) && c.unwrap_generic(ctyp).has_flag(.option) { + left.obj.typ = left.obj.typ.set_flag(.option) + } } } } else if right is ast.DumpExpr && right.expr is ast.ComptimeSelector { diff --git a/vlib/v/checker/postfix.v b/vlib/v/checker/postfix.v index 888e8a333..53b4cd24c 100644 --- a/vlib/v/checker/postfix.v +++ b/vlib/v/checker/postfix.v @@ -33,12 +33,26 @@ fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) ast.Type { return node.typ } } - typ_str := c.table.type_to_str(typ) - c.error('invalid operation: ${node.op.str()} (non-numeric type `${typ_str}`)', - node.pos) + if node.op != .question { + typ_str := c.table.type_to_str(typ) + c.error('invalid operation: ${node.op.str()} (non-numeric type `${typ_str}`)', + node.pos) + } else { + node.typ = c.unwrap_generic(c.type_resolver.get_type(node.expr)) + if node.op == .question { + node.typ = node.typ.clear_flag(.option) + } + return node.typ + } } else { if node.op != .question { node.auto_locked, _ = c.fail_if_immutable(mut node.expr) + } else { + node.typ = c.unwrap_generic(c.type_resolver.get_type(node.expr)) + if node.op == .question { + node.typ = node.typ.clear_flag(.option) + } + return node.typ } } node.typ = typ diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index cce7b968f..e19cdb317 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -436,6 +436,10 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.type_resolver.update_ct_type(left.name, ctyp) } } + } else if var_type.has_flag(.generic) && val is ast.StructInit + && val_type.has_flag(.generic) { + val_type = g.unwrap_generic(val_type) + var_type = val_type } } is_auto_heap = left.obj.is_auto_heap @@ -1048,6 +1052,14 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.expr_with_opt_or_block(val, val_type, left, var_type, false) } else if node.has_cross_var { g.gen_cross_tmp_variable(node.left, val) + } else if val is ast.None { + if var_type.has_flag(.generic) + && g.unwrap_generic(var_type).has_flag(.option) + && var_type.nr_muls() > 0 { + g.gen_option_error(var_type.set_nr_muls(0), ast.None{}) + } else { + g.gen_option_error(var_type, ast.None{}) + } } else { if op_overloaded { g.op_arg(val, op_expected_right, val_type) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4988524b1..c606fa936 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2370,13 +2370,24 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T } mut expr_is_fixed_array_var := false mut fn_option_clone := false + mut already_generated := false if ret_typ_is_option { if expr_typ_is_option && expr in [ast.StructInit, ast.ArrayInit, ast.MapInit] { - simple_assign = expr is ast.StructInit + simple_assign = expr is ast.StructInit && !expr.typ.has_flag(.generic) if simple_assign { g.write('${tmp_var} = ') } else { - g.write('builtin___option_none(&(${styp}[]) { ') + if expr is ast.StructInit && expr.typ.has_flag(.generic) { + // T{} + g.write('builtin___option_ok(&(${styp}[]) { ') + already_generated = true + g.expr(ast.StructInit{ + ...expr + typ: g.unwrap_generic(expr.typ).clear_flag(.option) + }) + } else { + g.write('builtin___option_none(&(${styp}[]) { ') + } } } else { simple_assign = @@ -2428,7 +2439,10 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T } else { g.write('builtin___result_ok(&(${styp}[]) { ') } - g.expr_with_cast(expr, expr_typ, ret_typ) + if !already_generated { + g.expr_with_cast(expr, expr_typ, ret_typ) + } + if fn_option_clone { g.write('.data') } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ea1d4c542..f6fb66c4a 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -782,6 +782,9 @@ 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.typ.has_flag(.generic) && typ.has_flag(.option) { + typ = typ.set_flag(.option_mut_param_t).set_nr_muls(param.typ.nr_muls() - 1) + } mut param_type_name := g.styp(typ) if param.typ.has_flag(.generic) { param_type_name = param_type_name.replace_each(c_fn_name_escape_seq) @@ -2423,11 +2426,7 @@ fn (mut g Gen) call_args(node ast.CallExpr) { if arg.expr.obj is ast.Var { if i < node.expected_arg_types.len && node.expected_arg_types[i].has_flag(.generic) && arg.expr.obj.ct_type_var !in [.generic_param, .no_comptime] { - exp_option := node.expected_arg_types[i].has_flag(.option) expected_types[i] = g.unwrap_generic(g.type_resolver.get_type(arg.expr)) - if !exp_option { - expected_types[i] = expected_types[i].clear_flag(.option) - } } else if arg.expr.obj.smartcasts.len > 0 { exp_sym := g.table.sym(expected_types[i]) orig_sym := g.table.sym(arg.expr.obj.orig_type) @@ -2731,7 +2730,7 @@ fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang as if arg.expr.is_lvalue() { if expected_type.has_flag(.option) { if expected_type.has_flag(.option_mut_param_t) { - g.write('(${g.styp(expected_type)})&') + g.write('&') } g.expr_with_opt(arg.expr, arg_typ, expected_type) return @@ -2834,7 +2833,11 @@ fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang as } // check if the argument must be dereferenced or not g.arg_no_auto_deref = is_smartcast && !arg_is_ptr && !exp_is_ptr && arg.should_be_ptr - g.expr_with_cast(arg.expr, arg_typ, expected_type) + if arg_typ.has_flag(.option) { + g.expr_with_opt(arg.expr, arg_typ, expected_type.set_flag(.option)) + } else { + g.expr_with_cast(arg.expr, arg_typ, expected_type) + } g.arg_no_auto_deref = false g.inside_smartcast = old_inside_smartcast if needs_closing { diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index e36cbb7b9..ded1f4814 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -123,14 +123,14 @@ fn (mut g Gen) struct_init(node ast.StructInit) { base_styp := g.styp(node.typ.clear_option_and_result()) g.writeln('${styp} ${tmp_var} = {0};') - if node.init_fields.len > 0 { + if node.init_fields.len > 0 || node.typ.has_flag(.generic) { g.write('builtin___option_ok(&(${base_styp}[]) { ') } else { g.write('builtin___option_none(&(${base_styp}[]) { ') } g.struct_init(ast.StructInit{ ...node - typ: node.typ.clear_option_and_result() + typ: g.unwrap_generic(node.typ).clear_option_and_result() }) g.writeln('}, (${option_name}*)&${tmp_var}, sizeof(${base_styp}));') g.empty_line = false diff --git a/vlib/v/tests/options/option_generic_array_test.v b/vlib/v/tests/options/option_generic_array_test.v new file mode 100644 index 000000000..c4ab6307b --- /dev/null +++ b/vlib/v/tests/options/option_generic_array_test.v @@ -0,0 +1,38 @@ +struct Decoder {} + +pub fn decode[T](val string) !T { + mut decoder := Decoder{} + mut result := T{} + decoder.decode_value(mut result)! + return result +} + +fn (mut decoder Decoder) decode_value[T](mut val T) ! { + $if T.unaliased_typ is $array { + decoder.decode_array(mut val)! + return + } $else $if T is $option { + val = none + } +} + +fn (mut decoder Decoder) decode_array[T](mut val []T) ! { + $if T is $option { + val << none + val << 1 + } $else { + val << 1 + } + + mut array_element := T{} + decoder.decode_value(mut array_element)! + val << array_element +} + +fn test_main() { + assert decode[[]int]('')! == [1, 0] + x := decode[[]?int]('')! + assert x[0] == ?int(none) + assert x[1] == ?int(1) + assert x[2] == none +} diff --git a/vlib/v/tests/options/option_generic_param_test.v b/vlib/v/tests/options/option_generic_param_test.v new file mode 100644 index 000000000..0f675c356 --- /dev/null +++ b/vlib/v/tests/options/option_generic_param_test.v @@ -0,0 +1,14 @@ +import json +import x.json2 + +type Elem = int | ?int + +const empty = ?int(none) +const array = [Elem(1), Elem(empty), 3] + +fn test_main() { + dump(array) + e := json.encode(array) + assert dump(e) == '[1,{},3]' + assert dump(json2.encode(array)) == '[1,,3]' +} diff --git a/vlib/v/type_resolver/generic_resolver.v b/vlib/v/type_resolver/generic_resolver.v index b7b1c657d..b6f5b3987 100644 --- a/vlib/v/type_resolver/generic_resolver.v +++ b/vlib/v/type_resolver/generic_resolver.v @@ -307,6 +307,9 @@ pub fn (mut t TypeResolver) resolve_args(cur_fn &ast.FnDecl, func &ast.Fn, mut n if param_typ.nr_muls() > 0 && comptime_args[k].nr_muls() > 0 { comptime_args[k] = comptime_args[k].set_nr_muls(0) } + if !param_typ.has_flag(.option) { + comptime_args[k] = comptime_args[k].clear_flag(.option) + } } else if mut call_arg.expr is ast.ComptimeCall { if call_arg.expr.method_name == 'method' { sym := t.table.sym(t.resolver.unwrap_generic(call_arg.expr.left_type)) diff --git a/vlib/x/json2/encode.v b/vlib/x/json2/encode.v index 1d53c9591..fefdc3c13 100644 --- a/vlib/x/json2/encode.v +++ b/vlib/x/json2/encode.v @@ -323,7 +323,7 @@ struct EncoderFieldInfo { is_required bool } -fn check_not_empty[T](val T) bool { +fn check_not_empty[T](val T) ?bool { $if val is string { if val == '' { return false @@ -332,6 +332,12 @@ fn check_not_empty[T](val T) bool { if val == 0 { return false } + } $else $if val is ?string { + return val ? != '' + } $else $if val is ?int { + return val ? != 0 + } $else $if val is ?f64 || val is ?f32 { + return val ? != 0.0 } return true } @@ -399,9 +405,9 @@ fn (mut encoder Encoder) encode_struct[T](val T) { if field_info.is_omitempty { $if value is $option { - write_field = check_not_empty(value) + write_field = check_not_empty(value) or { false } } $else { - write_field = check_not_empty(value) + write_field = check_not_empty(value) or { false } } } -- 2.39.5