From 8a8bb5e149eb35a635069082f58929abbd5dbdee Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 06:26:52 +0300 Subject: [PATCH] cgen: fix string interpolation of generic structs and methods returning generic struct references (fixes #16340) --- vlib/v/ast/table.v | 173 +++++++++++++++--- vlib/v/checker/fn.v | 8 + vlib/v/gen/c/cgen.v | 98 ++++++++-- vlib/v/gen/c/fn.v | 6 + vlib/v/gen/c/if.v | 25 +-- vlib/v/gen/c/infix.v | 5 +- vlib/v/gen/c/match.v | 48 ++--- vlib/v/gen/c/str_intp.v | 7 +- vlib/v/gen/c/utils.v | 124 ++++++++++++- .../tests/generics/generic_sumtype_str_test.v | 31 ++++ 10 files changed, 443 insertions(+), 82 deletions(-) diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 59de55eee..c88422855 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -2530,12 +2530,6 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str for t_typ in sym.generic_types { if !t_typ.has_flag(.generic) { t_to_types << t_typ - } else if t.sym(t_typ).kind == .any { - tname := t.sym(t_typ).name - index := generic_names.index(tname) - if index >= 0 && index < to_types.len { - t_to_types << to_types[index] - } } else { if tt := t.convert_generic_type(t_typ, generic_names, to_types) { t_to_types << tt @@ -3422,12 +3416,6 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str for t_typ in ts.generic_types { if !t_typ.has_flag(.generic) { t_concrete_types << t_typ - } else if t.sym(t_typ).kind == .any { - tname := t.sym(t_typ).name - index := generic_names.index(tname) - if index >= 0 && index < concrete_types.len { - t_concrete_types << concrete_types[index] - } } else { t_concrete_types << t.unwrap_generic_type(t_typ, generic_names, concrete_types) @@ -3444,6 +3432,7 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str gts := t.sym(ct) if ct.is_ptr() { nrt += '&'.repeat(ct.nr_muls()) + c_nrt += '__ptr__'.repeat(ct.nr_muls()) } nrt += gts.name c_nrt += gts.scoped_cname() @@ -3488,8 +3477,7 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str } } if final_concrete_types.len > 0 { - t.unwrap_method_types(ts, generic_names, concrete_types, - final_concrete_types) + t.unwrap_method_types(ts, generic_names, concrete_types) } } if idx <= 0 { @@ -3600,7 +3588,7 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str is_pub: ts.is_pub ) if final_concrete_types.len > 0 { - t.unwrap_method_types(ts, generic_names, concrete_types, final_concrete_types) + t.unwrap_method_types(ts, generic_names, concrete_types) } if new_idx <= 0 { existing := t.type_idxs[nrt] @@ -3646,7 +3634,7 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str is_pub: ts.is_pub ) if final_concrete_types.len > 0 { - t.unwrap_method_types(ts, generic_names, concrete_types, final_concrete_types) + t.unwrap_method_types(ts, generic_names, concrete_types) } if new_idx <= 0 { existing := t.type_idxs[nrt] @@ -3712,7 +3700,7 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str } } if final_concrete_types.len > 0 { - t.unwrap_method_types(ts, generic_names, concrete_types, final_concrete_types) + t.unwrap_method_types(ts, generic_names, concrete_types) } if new_idx <= 0 { // register_sym can fail when convert_generic_type (used above to @@ -3739,7 +3727,147 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str return typ } -fn (mut t Table) unwrap_method_types(ts &TypeSymbol, generic_names []string, concrete_types []Type, final_concrete_types []Type) { +fn concrete_type_lists_match(a []Type, b []Type) bool { + if a.len != b.len { + return false + } + for i, typ in a { + if typ != b[i] { + return false + } + } + return true +} + +fn (t &Table) type_contains_transformed_parent_inst(typ Type, parent_idx int, concrete_types []Type) bool { + if typ == 0 { + return false + } + sym := t.sym(typ) + match sym.info { + Array { + return t.type_contains_transformed_parent_inst(sym.info.elem_type, parent_idx, + concrete_types) + } + ArrayFixed { + return t.type_contains_transformed_parent_inst(sym.info.elem_type, parent_idx, + concrete_types) + } + Chan { + return t.type_contains_transformed_parent_inst(sym.info.elem_type, parent_idx, + concrete_types) + } + Thread { + return t.type_contains_transformed_parent_inst(sym.info.return_type, parent_idx, + concrete_types) + } + Map { + return + t.type_contains_transformed_parent_inst(sym.info.key_type, parent_idx, concrete_types) + || t.type_contains_transformed_parent_inst(sym.info.value_type, parent_idx, concrete_types) + } + FnType { + if t.type_contains_transformed_parent_inst(sym.info.func.return_type, parent_idx, + concrete_types) + { + return true + } + for param in sym.info.func.params { + if t.type_contains_transformed_parent_inst(param.typ, parent_idx, concrete_types) + || t.type_contains_transformed_parent_inst(param.orig_typ, parent_idx, concrete_types) { + return true + } + } + } + GenericInst { + if sym.info.parent_idx == parent_idx + && !concrete_type_lists_match(sym.info.concrete_types, concrete_types) { + return true + } + for ct in sym.info.concrete_types { + if t.type_contains_transformed_parent_inst(ct, parent_idx, concrete_types) { + return true + } + } + } + Struct { + if sym.parent_idx == parent_idx + && !concrete_type_lists_match(sym.info.concrete_types, concrete_types) { + return true + } + for ct in sym.info.concrete_types { + if t.type_contains_transformed_parent_inst(ct, parent_idx, concrete_types) { + return true + } + } + } + Interface { + if sym.parent_idx == parent_idx + && !concrete_type_lists_match(sym.info.concrete_types, concrete_types) { + return true + } + for ct in sym.info.concrete_types { + if t.type_contains_transformed_parent_inst(ct, parent_idx, concrete_types) { + return true + } + } + } + SumType { + if sym.parent_idx == parent_idx + && !concrete_type_lists_match(sym.info.concrete_types, concrete_types) { + return true + } + for ct in sym.info.concrete_types { + if t.type_contains_transformed_parent_inst(ct, parent_idx, concrete_types) { + return true + } + } + } + MultiReturn { + for mr_typ in sym.info.types { + if t.type_contains_transformed_parent_inst(mr_typ, parent_idx, concrete_types) { + return true + } + } + } + else {} + } + return false +} + +fn (mut t Table) should_auto_register_concrete_method(method Fn, parent_type Type, concrete_types []Type) bool { + parent_idx := parent_type.clear_flag(.generic).idx() + if parent_idx == 0 || method.generic_names.len != concrete_types.len { + return false + } + for i in 1 .. method.params.len { + param := method.params[i] + mut param_typ := param.typ + if param.typ.has_flag(.generic) || t.generic_type_names(param.typ).len > 0 { + if pt := t.convert_generic_param_type(param, method.generic_names, concrete_types) { + param_typ = pt + } else { + param_typ = t.unwrap_generic_type_ex(param.typ, method.generic_names, + concrete_types, true) + } + } + if t.type_contains_transformed_parent_inst(param_typ, parent_idx, concrete_types) { + return false + } + } + mut return_type := method.return_type + if method.return_type.has_flag(.generic) || t.generic_type_names(method.return_type).len > 0 { + if rt := t.convert_generic_type(method.return_type, method.generic_names, concrete_types) { + return_type = rt + } else { + return_type = t.unwrap_generic_type_ex(method.return_type, method.generic_names, + concrete_types, true) + } + } + return !t.type_contains_transformed_parent_inst(return_type, parent_idx, concrete_types) +} + +fn (mut t Table) unwrap_method_types(ts &TypeSymbol, generic_names []string, concrete_types []Type) { mut needs_unwrap_types := []Type{} for method in ts.get_methods() { for i in 1 .. method.params.len { @@ -3755,9 +3883,6 @@ fn (mut t Table) unwrap_method_types(ts &TypeSymbol, generic_names []string, con } } } - if final_concrete_types.len == method.generic_names.len { - t.register_fn_concrete_types(method.fkey(), final_concrete_types) - } } for typ_ in needs_unwrap_types { t.unwrap_generic_type(typ_, generic_names, concrete_types) @@ -3954,7 +4079,8 @@ pub fn (mut t Table) generic_insts_to_concrete() { parent_sym := t.sym(parent_info.parent_type) for method in parent_sym.methods { - if method.generic_names.len == info.concrete_types.len { + if method.generic_names.len == info.concrete_types.len + && t.should_auto_register_concrete_method(method, parent_info.parent_type, info.concrete_types) { t.register_fn_concrete_types(method.fkey(), info.concrete_types) } } @@ -4123,7 +4249,8 @@ pub fn (mut t Table) generic_insts_to_concrete() { sym.kind = parent.kind sym.generic_types = info.concrete_types.clone() for method in parent.methods { - if method.generic_names.len == info.concrete_types.len { + if method.generic_names.len == info.concrete_types.len + && t.should_auto_register_concrete_method(method, new_type(info.parent_idx).set_flag(.generic), info.concrete_types) { t.register_fn_concrete_types(method.fkey(), info.concrete_types) } } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 2e74afa37..1a2e790ef 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -794,6 +794,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.expected_type = ast.void_type saved_generic_names := node.generic_names mut needs_generic_names_restore := false + saved_return_type := node.return_type if c.table.cur_concrete_types.len > 0 && effective_generic_names.len == c.table.cur_concrete_types.len && node.generic_names != effective_generic_names { @@ -804,6 +805,12 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { needs_generic_names_restore = true } c.table.cur_fn = unsafe { node } + if c.table.cur_concrete_types.len > 0 { + resolved_return_type := c.recheck_concrete_type(node.return_type) + if resolved_return_type != ast.void_type && resolved_return_type != 0 { + node.return_type = resolved_return_type + } + } // Add return if `fn(...) ? {...}` have no return at end if node.return_type != ast.void_type && node.return_type.has_flag(.option) && (node.stmts.len == 0 || node.stmts.last() !is ast.Return) { @@ -963,6 +970,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { *p = saved_generic_names } } + node.return_type = saved_return_type } // check_same_type_ignoring_pointers util function to check if the Types are the same, including all diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index b4d038fd7..f079c6efb 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -213,7 +213,7 @@ mut: sumtype_casting_fns []SumtypeCastingFn anon_fn_definitions []string // anon generated functions definition list anon_fns shared []string // remove duplicate anon generated functions - sumtype_definitions map[u32]bool // `_TypeA_to_sumtype_TypeB()` fns that have been generated + sumtype_definitions map[string]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_pos map[ast.Type]token.Pos @@ -1273,6 +1273,17 @@ pub fn (mut g Gen) get_sumtype_variant_name(typ ast.Type, sym ast.TypeSymbol) st return if typ.has_flag(.option) { '_option_${sym.cname}' } else { sym.cname } } +// get_sumtype_casting_variant_name returns a helper-safe variant name that +// keeps pointer-depth distinctions for sumtype cast wrappers. +@[inline] +pub fn (mut g Gen) get_sumtype_casting_variant_name(typ ast.Type, sym ast.TypeSymbol) string { + mut variant_name := g.get_sumtype_variant_name(typ, sym) + if typ.nr_muls() > 0 { + variant_name += '__ptr__'.repeat(typ.nr_muls()) + } + return variant_name +} + pub fn (mut g Gen) write_typeof_functions() { g.writeln('') g.writeln('// >> typeof() support for sum types / interfaces') @@ -3692,8 +3703,8 @@ struct SumtypeCastingFn { } fn (mut g Gen) get_sumtype_casting_fn(got_ ast.Type, exp_ ast.Type) string { - mut got, exp := got_.idx_type(), exp_.idx_type() - i := u32(got) | u32(u32(exp) << 18) | u32(u32(exp_.has_flag(.option)) << 17) | u32(u32(got_.has_flag(.option)) << 16) + mut got, exp := got_, exp_.idx_type() + i := '${u32(got_)}:${u32(exp_)}' exp_sym := g.table.sym(exp) mut got_sym := g.table.sym(got) same_parent_sumtype_alias := got_sym.kind == .alias && g.table.unaliased_type(got_) == exp @@ -3703,7 +3714,7 @@ fn (mut g Gen) get_sumtype_casting_fn(got_ ast.Type, exp_ ast.Type) string { g.get_sumtype_variant_name(exp_, exp_sym) } // fn_name := '${got_sym.cname}_to_sumtype_${exp_sym.cname}' - sumtype_variant_name := g.get_sumtype_variant_name(got_, got_sym) + sumtype_variant_name := g.get_sumtype_casting_variant_name(got_, got_sym) fn_name := '${sumtype_variant_name}_to_sumtype_${cname}' if g.pref.experimental && fn_name.contains('v__ast__Struct_to_sumtype_v__ast__TypeInfo') { print_backtrace() @@ -3721,13 +3732,10 @@ fn (mut g Gen) get_sumtype_casting_fn(got_ ast.Type, exp_ ast.Type) string { g.sumtype_definitions[i] = true g.sumtype_casting_fns << SumtypeCastingFn{ fn_name: fn_name - got: if got_.has_flag(.option) { - new_got := ast.idx_to_type(got_sym.idx).set_flag(.option) - new_got - } else if same_parent_sumtype_alias { - got_.idx_type() + got: if same_parent_sumtype_alias { + got_ } else { - got_sym.idx + ast.idx_to_type(got_sym.idx).derive(got_) } exp: if exp_.has_flag(.option) { new_exp := exp.set_flag(.option) @@ -3774,6 +3782,7 @@ fn (mut g Gen) write_sumtype_casting_fn(fun SumtypeCastingFn) { sb.writeln('static inline ${exp_cname} ${fun.fn_name}(${got_cname} x) {') sb.writeln('\t${got_cname} ptr = x;') } else { + got_cname = g.styp(got) // g.definitions.writeln('${g.static_modifier} inline ${exp_cname} ${fun.fn_name}(${got_cname}* x);') // sb.writeln('${g.static_modifier} inline ${exp_cname} ${fun.fn_name}(${got_cname}* x) {') g.definitions.writeln('${exp_cname} ${fun.fn_name}(${got_cname}* x, bool is_mut);') @@ -3997,6 +4006,42 @@ fn (g &Gen) single_pointer_sumtype_nil_variant(expected_type ast.Type, expr ast. return variant } +@[inline] +fn (g &Gen) is_exact_sumtype_variant_match(variant ast.Type, got ast.Type) bool { + if variant.idx() != got.idx() || variant.has_flag(.option) != got.has_flag(.option) + || variant.nr_muls() != got.nr_muls() { + return false + } + variant_sym := g.table.sym(variant) + got_sym := g.table.sym(got) + if variant_sym.info is ast.FnType && got_sym.info is ast.FnType { + return g.table.fn_type_source_signature(variant_sym.info.func) == g.table.fn_type_source_signature(got_sym.info.func) + } + return true +} + +fn (g &Gen) find_matching_sumtype_variant(expected_type ast.Type, got_type ast.Type) ast.Type { + expected_sym := g.table.final_sym(expected_type) + if expected_sym.kind != .sum_type { + return got_type + } + variants := (expected_sym.info as ast.SumType).variants + for variant in variants { + if g.is_exact_sumtype_variant_match(variant, got_type) { + return variant + } + } + if got_type.is_any_kind_of_pointer() { + deref_got_type := got_type.deref() + for variant in variants { + if g.is_exact_sumtype_variant_match(variant, deref_got_type) { + return variant + } + } + } + return got_type +} + // use instead of expr() when you need a var to use as reference fn (mut g Gen) expr_with_var(expr ast.Expr, expected_type ast.Type, do_cast bool) string { stmt_str := g.go_before_last_stmt().trim_space() @@ -4374,8 +4419,21 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ sumtype_got_is_fn = sumtype_got_sym.kind == .function sumtype_got_is_ptr = sumtype_got_type.is_ptr() } + actual_sumtype_got_type := sumtype_got_type + sumtype_got_type = g.find_matching_sumtype_variant(unwrapped_expected_type, + sumtype_got_type) + sumtype_got_sym = g.table.sym(sumtype_got_type) + sumtype_got_styp = g.styp(sumtype_got_type) + sumtype_got_is_fn = sumtype_got_sym.kind == .function + sumtype_got_is_ptr = sumtype_got_type.is_ptr() fname := g.get_sumtype_casting_fn(sumtype_got_type, unwrapped_expected_type) + sumtype_cast_got_is_ptr := if sumtype_got_type.is_any_kind_of_pointer() + && g.is_exact_sumtype_variant_match(sumtype_got_type, actual_sumtype_got_type) { + false + } else { + got_is_ptr + } if expr is ast.ArrayInit && got_sym.kind == .array_fixed { stmt_str := g.go_before_last_stmt().trim_space() g.empty_line = true @@ -4385,11 +4443,11 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ g.writeln(';') g.write2(stmt_str, ' ') g.write('${fname}(&${tmp_var}, ${g.can_reuse_sumtype_variant_storage(unwrapped_expected_type, - got_is_ptr)})') + sumtype_cast_got_is_ptr)})') return } else { g.call_cfn_for_casting_expr(fname, expr, expected_type, sumtype_got_type, - unwrapped_exp_sym.cname, sumtype_got_is_ptr, sumtype_got_is_fn, + unwrapped_exp_sym.cname, sumtype_cast_got_is_ptr, sumtype_got_is_fn, sumtype_got_styp) } } @@ -8476,6 +8534,22 @@ fn (mut g Gen) return_stmt(node ast.Return) { } else { ast.void_type } + if exprs_len > 0 && type0 == ast.void_type { + fallback_type := match expr0 { + ast.IfExpr { expr0.typ } + ast.MatchExpr { expr0.return_type } + else { ast.void_type } + } + if fallback_type != ast.void_type && fallback_type != 0 { + type0 = g.unwrap_generic(g.recheck_concrete_type(fallback_type)) + } + } + if exprs_len > 0 && type0 == ast.void_type { + inferred_type := g.type_resolver.get_type_or_default(expr0, ast.void_type) + if inferred_type != ast.void_type && inferred_type != 0 { + type0 = g.unwrap_generic(g.recheck_concrete_type(inferred_type)) + } + } // When the expression uses `?` or `!` propagation, the evaluated type is // unwrapped (the option/result flag should be stripped). if exprs_len > 0 { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 8f6a0a4dc..88a353432 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -5084,6 +5084,12 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { if typ.has_flag(.option) && expr.or_expr.kind != .absent { typ = typ.clear_flag(.option) } + if typ.has_flag(.generic) || g.type_has_unresolved_generic_parts(typ) { + resolved_typ := g.resolved_expr_type(expr, typ) + if resolved_typ != 0 && resolved_typ != ast.void_type { + typ = resolved_typ + } + } } typ_sym := g.table.sym(typ) needs_tmp_string := !typ.has_option_or_result() diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index fe04c1d8d..563ef1cf4 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -210,6 +210,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { if use_outer_tmp { g.outer_tmp_var = '' } + resolved_node_typ := g.infer_if_expr_type(node) // For simple if expressions we can use C's `?:` // `if x > 0 { 1 } else { 2 }` => `(x > 0)? (1) : (2)` @@ -222,7 +223,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { tmp := if use_outer_tmp { // Use the tmp var from outer context (e.g. from stmts_with_tmp_var) saved_outer_tmp_var - } else if g.inside_if_option || (node.typ != ast.void_type && needs_tmp_var) { + } else if g.inside_if_option || (resolved_node_typ != ast.void_type && needs_tmp_var) { g.new_tmp_var() } else { '' @@ -234,9 +235,9 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { if needs_tmp_var { exit_label = g.new_tmp_var() node_typ := if g.inside_or_block { - node.typ.clear_option_and_result() + resolved_node_typ.clear_option_and_result() } else { - node.typ + resolved_node_typ } // For generic functions, if the if-expression's type was set to a concrete type // by the checker but we're generating a different generic instance, we need to @@ -275,8 +276,8 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { mut styp := g.styp(resolved_typ) if (g.inside_if_option || node_typ.has_flag(.option)) && !g.inside_or_block { raw_state = g.inside_if_option - if node.typ != ast.void_type { - g.last_if_option_type = node.typ + if resolved_node_typ != ast.void_type { + g.last_if_option_type = resolved_node_typ defer(fn) { g.last_if_option_type = tmp_if_option_type } @@ -304,7 +305,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { if tmp != '' && !use_outer_tmp { // Only declare the tmp var if it's not from outer context mut declared_tmp := false - if node.typ == ast.void_type && g.last_if_option_type != 0 { + if resolved_node_typ == ast.void_type && g.last_if_option_type != 0 { // nested if on return stmt g.write2(g.styp(g.unwrap_generic(g.last_if_option_type)), ' ') } else if resolved_sym.kind == .function && resolved_sym.info is ast.FnType { @@ -337,9 +338,9 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { g.write(' ? ') } prev_expected_cast_type := g.expected_cast_type - if node.is_expr - && (g.table.sym(node.typ).kind == .sum_type || node.typ.has_flag(.shared_f)) { - g.expected_cast_type = node.typ + if node.is_expr && (g.table.sym(resolved_node_typ).kind == .sum_type + || resolved_node_typ.has_flag(.shared_f)) { + g.expected_cast_type = resolved_node_typ } g.stmts(branch.stmts) g.expected_cast_type = prev_expected_cast_type @@ -634,9 +635,9 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } if needs_tmp_var { prev_expected_cast_type := g.expected_cast_type - if node.is_expr - && (g.table.sym(node.typ).kind == .sum_type || node.typ.has_flag(.shared_f)) { - g.expected_cast_type = node.typ + if node.is_expr && (g.table.sym(resolved_node_typ).kind == .sum_type + || resolved_node_typ.has_flag(.shared_f)) { + g.expected_cast_type = resolved_node_typ } g.stmts_with_tmp_var(branch.stmts, tmp) g.write_defer_stmts(branch.scope, false, node.pos) diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 600455fb3..81b3bc41e 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -128,7 +128,10 @@ fn (mut g Gen) infix_expr_arrow_op(node ast.InfixExpr) { fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { mut left_type := g.type_resolver.get_type_or_default(node.left, node.left_type) mut right_type := g.type_resolver.get_type_or_default(node.right, node.right_type) - if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { + if (g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) + || left_type.has_flag(.generic) || right_type.has_flag(.generic) + || g.type_has_unresolved_generic_parts(left_type) + || g.type_has_unresolved_generic_parts(right_type) { resolved_left := g.resolved_expr_type(node.left, node.left_type) if resolved_left != 0 { // Don't override a smartcasted concrete type (from the checker) diff --git a/vlib/v/gen/c/match.v b/vlib/v/gen/c/match.v index 6e179007f..3206c1c59 100644 --- a/vlib/v/gen/c/match.v +++ b/vlib/v/gen/c/match.v @@ -14,12 +14,13 @@ fn (g &Gen) match_cond_can_use_directly(cond ast.Expr) bool { } fn (mut g Gen) need_tmp_var_in_match(node ast.MatchExpr) bool { - if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 { + resolved_return_type := g.infer_match_expr_type(node) + if node.is_expr && resolved_return_type != ast.void_type && resolved_return_type != 0 { if g.inside_struct_init { return true } - if g.table.sym(node.return_type).kind in [.sum_type, .interface, .multi_return] - || node.return_type.has_option_or_result() { + if g.table.sym(resolved_return_type).kind in [.sum_type, .interface, .multi_return] + || resolved_return_type.has_option_or_result() { return true } if g.table.final_sym(node.cond_type).kind == .enum && node.branches.len > 5 { @@ -60,8 +61,9 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) { g.writeln('// match 0') return } + resolved_return_type := g.infer_match_expr_type(node) need_tmp_var := g.need_tmp_var_in_match(node) - is_expr := (node.is_expr && node.return_type != ast.void_type) || g.inside_ternary > 0 + is_expr := (node.is_expr && resolved_return_type != ast.void_type) || g.inside_ternary > 0 mut cond_var := '' mut tmp_var := '' @@ -70,13 +72,13 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) { g.inside_ternary++ } if is_expr { - if node.return_type.has_flag(.option) { + if resolved_return_type.has_flag(.option) { old := g.inside_match_option defer(fn) { g.inside_match_option = old } g.inside_match_option = true - } else if node.return_type.has_flag(.result) { + } else if resolved_return_type.has_flag(.result) { old := g.inside_match_result defer(fn) { g.inside_match_result = old @@ -105,18 +107,18 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) { cur_line = g.go_before_last_stmt().trim_left(' \t') tmp_var = g.new_tmp_var() mut func_decl := '' - ret_final_sym := g.table.final_sym(node.return_type) - if !node.return_type.has_option_or_result() && ret_final_sym.kind == .function { + ret_final_sym := g.table.final_sym(resolved_return_type) + if !resolved_return_type.has_option_or_result() && ret_final_sym.kind == .function { if ret_final_sym.info is ast.FnType { def := g.fn_var_signature(ast.void_type, ret_final_sym.info.func.return_type, ret_final_sym.info.func.params.map(it.typ), tmp_var) - func_decl = '${def} = &${g.styp(node.return_type)};' + func_decl = '${def} = &${g.styp(resolved_return_type)};' } } if func_decl != '' { g.writeln(func_decl) // func, anon func declaration } else { - g.writeln('${g.styp(node.return_type)} ${tmp_var} = ${g.type_default(node.return_type)};') + g.writeln('${g.styp(resolved_return_type)} ${tmp_var} = ${g.type_default(resolved_return_type)};') } g.empty_line = true if g.infix_left_var_name.len > 0 { @@ -130,7 +132,7 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) { g.write('(') } if node.is_sum_type { - g.match_expr_sumtype(node, is_expr, cond_var, tmp_var) + g.match_expr_sumtype(node, is_expr, cond_var, tmp_var, resolved_return_type) } else { cond_fsym := g.table.final_sym(node.cond_type) enum_is_multi_allowed := cond_fsym.info is ast.Enum && cond_fsym.info.is_multi_allowed @@ -153,13 +155,13 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) { // eprintln('> can_be_a_switch: ${can_be_a_switch}') if can_be_a_switch && !is_expr && g.loop_depth == 0 && g.fn_decl != unsafe { nil } && cond_fsym.is_int() && !enum_is_multi_allowed { - g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym) + g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym, resolved_return_type) } else if cond_fsym.kind == .enum && g.loop_depth == 0 && node.branches.len > 5 && g.fn_decl != unsafe { nil } && !enum_is_multi_allowed { // do not optimize while in top-level - g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym) + g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym, resolved_return_type) } else { - g.match_expr_classic(node, is_expr, cond_var, tmp_var) + g.match_expr_classic(node, is_expr, cond_var, tmp_var, resolved_return_type) } } g.set_current_pos_as_last_stmt_pos() @@ -181,7 +183,7 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) { } } -fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { +fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, resolved_return_type ast.Type) { dot_or_ptr := g.dot_or_ptr(node.cond_type) use_ternary := is_expr && tmp_var == '' cond_sym := g.table.final_sym(node.cond_type) @@ -252,8 +254,8 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str } } if is_expr && tmp_var.len > 0 - && g.table.sym(node.return_type).kind in [.sum_type, .interface] { - g.expected_cast_type = node.return_type + && g.table.sym(resolved_return_type).kind in [.sum_type, .interface] { + g.expected_cast_type = resolved_return_type } inside_interface_deref_old := g.inside_interface_deref if is_expr && branch.stmts.len > 0 { @@ -288,7 +290,7 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str } } -fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, cond_fsym ast.TypeSymbol) { +fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, cond_fsym ast.TypeSymbol, resolved_return_type ast.Type) { node_cond_type_unsigned := node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type] covered_enum_cap := if cond_fsym.info is ast.Enum { cond_fsym.info.vals.len } else { 0 } @@ -362,8 +364,8 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri } g.writeln('{') if is_expr && tmp_var.len > 0 - && g.table.sym(node.return_type).kind in [.sum_type, .interface] { - g.expected_cast_type = node.return_type + && g.table.sym(resolved_return_type).kind in [.sum_type, .interface] { + g.expected_cast_type = resolved_return_type } ends_with_return := g.stmts_with_tmp_var(branch.stmts, tmp_var) g.expected_cast_type = 0 @@ -439,7 +441,7 @@ fn (mut g Gen) should_check_low_bound_in_range_expr(expr ast.RangeExpr, node_con return should_check_low_bound } -fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { +fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string, resolved_return_type ast.Type) { node_cond_type_unsigned := node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type] type_sym := g.table.final_sym(node.cond_type) use_ternary := is_expr && tmp_var == '' @@ -572,8 +574,8 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str } } if is_expr && tmp_var.len > 0 - && g.table.sym(node.return_type).kind in [.sum_type, .interface] { - g.expected_cast_type = node.return_type + && g.table.sym(resolved_return_type).kind in [.sum_type, .interface] { + g.expected_cast_type = resolved_return_type } g.stmts_with_tmp_var(branch.stmts, tmp_var) g.write_defer_stmts(branch.scope, false, node.pos) diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 56c34b47b..cd5d73434 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -83,7 +83,8 @@ fn int_ref_interpolates_as_value(expr ast.Expr, typ ast.Type, fmt u8) bool { return match expr { ast.Ident { if expr.obj is ast.Var { - expr.obj.is_arg || (expr.obj.expr is ast.PrefixExpr && expr.obj.expr.op == .amp) + expr.obj.is_arg || expr.obj.expr is ast.AsCast + || (expr.obj.expr is ast.PrefixExpr && expr.obj.expr.op == .amp) } else { false } @@ -500,6 +501,10 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { mut node_ := unsafe { node } mut fmts := node_.fmts.clone() for i, mut expr in node_.exprs { + if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 && !node_.need_fmts[i] + && fmts[i] != `_` { + fmts[i] = `_` + } mut field_typ := if g.is_type_name_string_expr(expr) { ast.string_type } else if mut expr is ast.AsCast { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 3f25205a5..4bca60ae3 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -93,6 +93,58 @@ fn (mut g Gen) promote_literal_array_type(typ ast.Type) ast.Type { return typ } +fn (mut g Gen) infer_branch_expr_type(stmts []ast.Stmt) ast.Type { + if stmts.len == 0 { + return ast.void_type + } + last_stmt := stmts.last() + if last_stmt !is ast.ExprStmt { + return ast.void_type + } + expr_stmt := last_stmt as ast.ExprStmt + mut default_typ := expr_stmt.typ + if default_typ == 0 || default_typ == ast.void_type { + default_typ = expr_stmt.expr.type() + } + mut resolved_typ := g.resolved_expr_type(expr_stmt.expr, default_typ) + if resolved_typ == 0 || resolved_typ == ast.void_type { + resolved_typ = g.type_resolver.get_type_or_default(expr_stmt.expr, default_typ) + } + if resolved_typ == 0 || resolved_typ == ast.void_type { + resolved_typ = default_typ + } + if resolved_typ == 0 || resolved_typ == ast.void_type { + return ast.void_type + } + return g.unwrap_generic(g.recheck_concrete_type(resolved_typ)) +} + +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)) + } + for branch in node.branches { + branch_typ := g.infer_branch_expr_type(branch.stmts) + if branch_typ != 0 && branch_typ != ast.void_type { + return branch_typ + } + } + return ast.void_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)) + } + for branch in node.branches { + branch_typ := g.infer_branch_expr_type(branch.stmts) + if branch_typ != 0 && branch_typ != ast.void_type { + return branch_typ + } + } + return ast.void_type +} + fn (mut g Gen) recheck_concrete_type(typ ast.Type) ast.Type { if typ == 0 { return typ @@ -199,12 +251,21 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { v.is_unwrapped = false } } - if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 && !v.is_arg - && v.expr !is ast.EmptyExpr && v.pos.pos > 0 && v.pos.pos < expr.pos.pos - && !(v.expr is ast.Ident && v.expr.name == expr.name) { + if !v.is_arg && v.expr !is ast.EmptyExpr && v.pos.pos > 0 && v.pos.pos < expr.pos.pos + && !(v.expr is ast.Ident && v.expr.name == expr.name) + && ((g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) + || v.typ.has_flag(.generic) + || g.type_has_unresolved_generic_parts(v.typ)) { resolved_expr_type := g.resolved_expr_type(v.expr, v.typ) if resolved_expr_type != 0 { refreshed_expr_type = g.unwrap_generic(g.recheck_concrete_type(resolved_expr_type)) + if g.type_has_unresolved_generic_parts(refreshed_expr_type) { + call_like_type := g.resolved_call_like_expr_type(v.expr) + if call_like_type != 0 && !call_like_type.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(call_like_type) { + refreshed_expr_type = call_like_type + } + } // If the variable was initialized with an `or {}` block that // unwraps the option/result, clear the flag from the resolved type if refreshed_expr_type.has_option_or_result() && g.expr_has_or_block(v.expr) { @@ -247,14 +308,23 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { parent_v.typ = refreshed_parent_type } } - if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 && !parent_v.is_arg - && parent_v.expr !is ast.EmptyExpr && parent_v.pos.pos > 0 + if !parent_v.is_arg && parent_v.expr !is ast.EmptyExpr && parent_v.pos.pos > 0 && parent_v.pos.pos < expr.pos.pos && !(parent_v.expr is ast.Ident - && parent_v.expr.name == expr.name) { + && parent_v.expr.name == expr.name) + && ((g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) + || parent_v.typ.has_flag(.generic) + || g.type_has_unresolved_generic_parts(parent_v.typ)) { resolved_parent_expr_type := g.resolved_expr_type(parent_v.expr, parent_v.typ) if resolved_parent_expr_type != 0 { parent_v.typ = g.unwrap_generic(g.recheck_concrete_type(resolved_parent_expr_type)) + if g.type_has_unresolved_generic_parts(parent_v.typ) { + call_like_type := g.resolved_call_like_expr_type(parent_v.expr) + if call_like_type != 0 && !call_like_type.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(call_like_type) { + parent_v.typ = call_like_type + } + } } } if v.is_unwrapped { @@ -295,10 +365,22 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { } return g.unwrap_generic(g.recheck_concrete_type(smartcast_type)) } - if parent_v.expr !is ast.EmptyExpr { + if parent_v.expr !is ast.EmptyExpr + && ((g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) + || parent_v.typ.has_flag(.generic) + || g.type_has_unresolved_generic_parts(parent_v.typ)) { resolved_parent_type := g.resolved_expr_type(parent_v.expr, parent_v.typ) if resolved_parent_type != 0 { - return g.unwrap_generic(g.recheck_concrete_type(resolved_parent_type)) + resolved_parent := + g.unwrap_generic(g.recheck_concrete_type(resolved_parent_type)) + if g.type_has_unresolved_generic_parts(resolved_parent) { + call_like_type := g.resolved_call_like_expr_type(parent_v.expr) + if call_like_type != 0 && !call_like_type.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(call_like_type) { + return call_like_type + } + } + return resolved_parent } } if parent_v.typ != 0 { @@ -678,12 +760,22 @@ fn (mut g Gen) resolved_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type return scope_type } } - if expr.obj.expr !is ast.EmptyExpr && (expr.obj.ct_type_var == .generic_var + if expr.obj.expr !is ast.EmptyExpr + && (expr.obj.ct_type_var == .generic_var || expr.obj.typ.has_flag(.generic) + || g.type_has_unresolved_generic_parts(expr.obj.typ) || ((g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) && expr.obj.expr !in [ast.IntegerLiteral, ast.FloatLiteral, ast.StringLiteral, ast.BoolLiteral, ast.CharLiteral])) { if !(expr.obj.expr is ast.Ident && expr.obj.expr.name == expr.name) { - resolved := g.resolved_expr_type(expr.obj.expr, expr.obj.typ) + mut resolved := g.resolved_expr_type(expr.obj.expr, expr.obj.typ) if resolved != 0 { + resolved = g.unwrap_generic(g.recheck_concrete_type(resolved)) + if g.type_has_unresolved_generic_parts(resolved) { + call_like_type := g.resolved_call_like_expr_type(expr.obj.expr) + if call_like_type != 0 && !call_like_type.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(call_like_type) { + return call_like_type + } + } return g.unwrap_generic(resolved) } } @@ -895,6 +987,18 @@ fn (mut g Gen) resolved_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type return g.unwrap_generic(left_type) } } + ast.IfExpr { + inferred_typ := g.infer_if_expr_type(expr) + if inferred_typ != 0 && inferred_typ != ast.void_type { + return inferred_typ + } + } + ast.MatchExpr { + inferred_typ := g.infer_match_expr_type(expr) + if inferred_typ != 0 && inferred_typ != ast.void_type { + return inferred_typ + } + } ast.CallExpr { if expr.kind == .type_name { return ast.string_type diff --git a/vlib/v/tests/generics/generic_sumtype_str_test.v b/vlib/v/tests/generics/generic_sumtype_str_test.v index 94152564d..ca720629c 100644 --- a/vlib/v/tests/generics/generic_sumtype_str_test.v +++ b/vlib/v/tests/generics/generic_sumtype_str_test.v @@ -15,6 +15,22 @@ pub fn some[T](v T) Maybe[T] { return Maybe[T](v) } +pub fn noth[T]() Maybe[T] { + return Maybe[T](None{}) +} + +pub fn (m &Maybe[T]) as_ref[T]() Maybe[&T] { + return match m { + None { + noth[&T]() + } + T { + mut ref := voidptr(unsafe { &m }) + some[&T](ref) + } + } +} + fn test_generic_sumtype_str() { a := some(123) b := some('abc') @@ -29,3 +45,18 @@ fn test_generic_sumtype_str() { println('${b}') assert '${b}' == 'Some(abc)' } + +fn test_generic_sumtype_str_with_ref_variant() { + value := 123 + ptr := &value + ref_sum := some[&int](ptr) + assert typeof(ref_sum).name == 'Maybe[&int]' + assert ref_sum.str() == 'Some(123)' + assert '${ref_sum}' == 'Some(123)' + + sum := some(123) + sum_ref := sum.as_ref() + assert typeof(sum_ref).name == 'Maybe[&int]' + assert sum_ref.str() == 'Some(123)' + assert '${sum_ref}' == 'Some(123)' +} -- 2.39.5