From 77b4117c563692762ae5bc7bb93269cfcc25aecb Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 15 Jun 2026 18:26:33 +0300 Subject: [PATCH] cgen: match aliases by runtime tag (#27461) --- vlib/v/checker/infix.v | 12 +- vlib/v/gen/c/cgen.v | 132 +++++++----- vlib/v/gen/c/infix.v | 153 ++++++++++--- vlib/v/gen/c/match.v | 28 +-- .../testdata/msvc_return_alias_is.c.must_have | 3 + vlib/v/gen/c/testdata/msvc_return_alias_is.vv | 14 ++ vlib/v/gen/c/utils.v | 126 +++++++++++ .../aliases/alias_is_match_runtime_tag_test.v | 201 ++++++++++++++++++ .../tests/generics/generic_sumtype_str_test.v | 16 ++ .../sumtypes/aggregate_is_nodetype_test.v | 17 ++ 10 files changed, 602 insertions(+), 100 deletions(-) create mode 100644 vlib/v/gen/c/testdata/msvc_return_alias_is.c.must_have create mode 100644 vlib/v/gen/c/testdata/msvc_return_alias_is.vv create mode 100644 vlib/v/tests/aliases/alias_is_match_runtime_tag_test.v diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index c42716829..12b45dfbc 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -984,8 +984,10 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { if typ_sym.kind == .placeholder { c.error('${op}: type `${typ_sym.name}` does not exist', right_expr.pos()) } + mut left_sumtype_check_type := left_type if mut left_sym.info is ast.Aggregate { parent_left_type := left_sym.info.sum_type + left_sumtype_check_type = parent_left_type left_sym = c.table.sym(parent_left_type) } if c.inside_sql { @@ -1002,8 +1004,9 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } else { typ } - if variant_typ !in left_sym.info.variants - && c.unwrap_generic(variant_typ) !in left_sym.info.variants { + unwrapped_variant_typ := c.unwrap_generic(variant_typ) + if !c.table.sumtype_has_variant_recursive(left_sumtype_check_type, variant_typ, true) + && !c.table.sumtype_has_variant_recursive(left_sumtype_check_type, unwrapped_variant_typ, true) { c.error('`${left_sym.name}` has no variant `${typ_sym.name}`', right_pos) } } else if c.is_orig_sumtype(node.left) { @@ -1013,8 +1016,9 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { orig_t := (node.left.obj as ast.Var).orig_type orig_sym := c.table.sym(orig_t) if orig_sym.info is ast.SumType { - if typ !in orig_sym.info.variants - && c.unwrap_generic(typ) !in orig_sym.info.variants { + unwrapped_typ := c.unwrap_generic(typ) + if !c.table.sumtype_has_variant_recursive(orig_t, typ, true) + && !c.table.sumtype_has_variant_recursive(orig_t, unwrapped_typ, true) { c.error('`${orig_sym.name}` has no variant `${typ_sym.name}`', right_pos) } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c9205b833..9d7663ad5 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -4619,6 +4619,12 @@ fn (g &Gen) find_matching_sumtype_variant(expected_type ast.Type, got_type ast.T return variant } } + got_unaliased := g.table.fully_unaliased_type(got_type) + for variant in variants { + if g.table.fully_unaliased_type(variant) == got_unaliased { + return variant + } + } for variant in variants { if g.table.can_implicit_array_cast(got_type, variant) { return variant @@ -12814,6 +12820,42 @@ fn (mut g Gen) as_cast_option_payload_expr_from_expr(typ ast.Type, expr ast.Expr return g.as_cast_option_payload_expr(typ, g.expr_string(expr), false) } +fn (mut g Gen) write_as_cast_call_start(styp string, sym ast.TypeSymbol) { + if sym.info is ast.FnType { + g.write('(${styp})') + } else if g.inside_smartcast { + g.write('(${styp}*)') + } else { + g.write('*(${styp}*)') + } +} + +fn (mut g Gen) write_as_cast_call(obj_expr string, tag_expr string, expected_sidx string, index_exprs []string) { + needs_tag_condition := index_exprs.len > 1 + || (index_exprs.len == 1 && index_exprs[0] != expected_sidx) + if needs_tag_condition { + g.write('(') + g.write_type_tag_condition(tag_expr, '==', index_exprs) + g.write(' ? ${obj_expr} : ') + } + g.write('builtin____as_cast(${obj_expr}, ${tag_expr}, ${expected_sidx})') + if needs_tag_condition { + g.write(')') + } +} + +fn (mut g Gen) as_cast_payload_type(target_type ast.Type, matching_variants []ast.Type) ast.Type { + for variant in matching_variants { + if g.is_exact_sumtype_variant_match(variant, target_type) { + return target_type + } + } + if matching_variants.len > 0 { + return matching_variants[0] + } + return target_type +} + fn (mut g Gen) as_cast(node ast.AsCast) { // Make sure the sum type can be cast to this type (the types // are the same), otherwise panic. @@ -12845,6 +12887,11 @@ fn (mut g Gen) as_cast(node ast.AsCast) { if mut expr_type_sym.info is ast.SumType { expr_is_option := unwrapped_expr_type.has_flag(.option) dot := if expr_type_without_option.is_ptr() { '->' } else { '.' } + matching_variants := g.matching_sumtype_variant_types(expr_type_without_option, + unwrapped_node_typ) + index_exprs := g.type_idx_exprs_for_types(matching_variants) + payload_sym := g.table.sym(g.as_cast_payload_type(unwrapped_node_typ, matching_variants)) + sidx := g.type_sidx(unwrapped_node_typ) if node.expr.has_fn_call() && !g.is_cc_msvc { tmp_var := g.new_tmp_var() expr_styp := g.styp(node.expr_type) @@ -12856,41 +12903,21 @@ fn (mut g Gen) as_cast(node ast.AsCast) { } else { tmp_var } - if sym.info is ast.FnType { - g.write('(${styp})builtin____as_cast(') - } else if g.inside_smartcast { - g.write('(${styp}*)builtin____as_cast(') - } else { - g.write('*(${styp}*)builtin____as_cast(') - } - g.write2('(${expr_str})', dot) - g.write2('_${sym.cname},', '(${expr_str})') - g.write(dot) - sidx := g.type_sidx(unwrapped_node_typ) - g.write('_typ, ${sidx}); })') + obj_expr := '(${expr_str})${dot}_${payload_sym.cname}' + tag_expr := '(${expr_str})${dot}_typ' + g.write_as_cast_call_start(styp, sym) + g.write_as_cast_call(obj_expr, tag_expr, sidx, index_exprs) + g.write('; })') } else { - if sym.info is ast.FnType { - g.write('(${styp})builtin____as_cast(') - } else if g.inside_smartcast { - g.write('(${styp}*)builtin____as_cast(') - } else { - g.write('*(${styp}*)builtin____as_cast(') - } - if expr_is_option { - expr_str := g.as_cast_option_payload_expr_from_expr(unwrapped_expr_type, node.expr) - g.write2('(${expr_str})', dot) - g.write2('_${sym.cname},', '(${expr_str})') - g.write(dot) + expr_str := if expr_is_option { + g.as_cast_option_payload_expr_from_expr(unwrapped_expr_type, node.expr) } else { - g.write('(') - g.expr(node.expr) - g.write2(')', dot) - g.write2('_${sym.cname},', '(') - g.expr(node.expr) - g.write2(')', dot) + g.expr_string(node.expr) } - sidx := g.type_sidx(unwrapped_node_typ) - g.write('_typ, ${sidx})') + obj_expr := '(${expr_str})${dot}_${payload_sym.cname}' + tag_expr := '(${expr_str})${dot}_typ' + g.write_as_cast_call_start(styp, sym) + g.write_as_cast_call(obj_expr, tag_expr, sidx, index_exprs) } // fill as cast name table @@ -12934,40 +12961,27 @@ fn (mut g Gen) as_cast(node ast.AsCast) { expr_type_sym.info = info } else if mut expr_type_sym.info is ast.Interface && node.expr_type != node.typ { dot := if node.expr_type.is_ptr() { '->' } else { '.' } + matching_variants := g.matching_interface_variant_types(expr_type_sym, unwrapped_node_typ) + index_exprs := g.type_idx_exprs_for_types(matching_variants) + payload_sym := g.table.sym(g.as_cast_payload_type(unwrapped_node_typ, matching_variants)) + sidx := g.type_sidx(unwrapped_node_typ) if node.expr.has_fn_call() && !g.is_cc_msvc { tmp_var := g.new_tmp_var() expr_styp := g.styp(node.expr_type) g.write('({ ${expr_styp} ${tmp_var} = ') g.expr(node.expr) g.write('; ') - if sym.info is ast.FnType { - g.write('(${styp})builtin____as_cast(') - } else if g.inside_smartcast { - g.write('(${styp}*)builtin____as_cast(') - } else { - g.write('*(${styp}*)builtin____as_cast(') - } - g.write2(tmp_var, dot) - g.write('_${sym.cname},v_typeof_interface_idx_${expr_type_sym.cname}(') - g.write2(tmp_var, dot) - sidx := g.type_sidx(unwrapped_node_typ) - g.write('_typ), ${sidx}); })') + obj_expr := '${tmp_var}${dot}_${payload_sym.cname}' + tag_expr := 'v_typeof_interface_idx_${expr_type_sym.cname}(${tmp_var}${dot}_typ)' + g.write_as_cast_call_start(styp, sym) + g.write_as_cast_call(obj_expr, tag_expr, sidx, index_exprs) + g.write('; })') } else { - if sym.info is ast.FnType { - g.write('(${styp})builtin____as_cast(') - } else if g.inside_smartcast { - g.write('(${styp}*)builtin____as_cast(') - } else { - g.write('*(${styp}*)builtin____as_cast(') - } - g.write('(') - g.expr(node.expr) - g.write2(')', dot) - g.write2('_${sym.cname},v_typeof_interface_idx_${expr_type_sym.cname}(', '(') - g.expr(node.expr) - g.write2(')', dot) - sidx := g.type_sidx(unwrapped_node_typ) - g.write('_typ), ${sidx})') + expr_str := g.expr_string(node.expr) + obj_expr := '(${expr_str})${dot}_${payload_sym.cname}' + tag_expr := 'v_typeof_interface_idx_${expr_type_sym.cname}((${expr_str})${dot}_typ)' + g.write_as_cast_call_start(styp, sym) + g.write_as_cast_call(obj_expr, tag_expr, sidx, index_exprs) } // fill as cast name table diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 26ddf9e4d..2f35cf2c7 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -1106,6 +1106,87 @@ fn (mut g Gen) infix_expr_in_optimization(left ast.Expr, left_type ast.Type, rig } } +fn (mut g Gen) type_tag_expr_for_is_left(node ast.InfixExpr, is_aggregate bool, is_orig_sumtype bool) string { + mut left_expr := '' + if is_aggregate { + left_expr = '${node.left}' + } else { + if is_orig_sumtype { + g.prevent_sum_type_unwrapping_once = true + } + left_expr = g.expr_string(node.left) + } + left_value_expr := if node.left_type.nr_muls() > 1 { + '(${'*'.repeat(node.left_type.nr_muls() - 1)}${left_expr})' + } else { + '(${left_expr})' + } + dot_or_ptr := if node.left_type.is_ptr() { '->' } else { '.' } + return '${left_value_expr}${dot_or_ptr}_typ' +} + +fn (mut g Gen) write_type_tag_expr_for_is_left(node ast.InfixExpr, is_aggregate bool, is_orig_sumtype bool) { + g.write('(') + if node.left_type.nr_muls() > 1 { + g.write('*'.repeat(node.left_type.nr_muls() - 1)) + } + if is_aggregate { + g.write('${node.left}') + } else if is_orig_sumtype { + g.prevent_sum_type_unwrapping_once = true + g.expr(node.left) + } else { + g.expr(node.left) + } + g.write(')') + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write('_typ') +} + +fn (mut g Gen) write_is_type_tag_condition(node ast.InfixExpr, is_aggregate bool, is_orig_sumtype bool, cmp_op string, index_exprs []string) { + if index_exprs.len == 0 { + g.write(if cmp_op == '==' { 'false' } else { 'true' }) + return + } + if index_exprs.len == 1 { + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write(' ${cmp_op} ${index_exprs[0]}') + return + } + if is_aggregate { + tag_expr := g.type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write_type_tag_condition(tag_expr, cmp_op, index_exprs) + return + } + tag_tmp := g.new_tmp_var() + if !g.is_cc_msvc { + g.write('({ ${ast.int_type_name} ${tag_tmp} = ') + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write('; ') + g.write_type_tag_condition(tag_tmp, cmp_op, index_exprs) + g.write('; })') + return + } + mut cur_line := if g.inside_ternary > 0 { + g.go_before_ternary().trim_space() + } else { + g.go_before_last_stmt().trim_space() + } + if g.inside_return && cur_line.ends_with('return') { + cur_line += ' ' + } + g.empty_line = true + g.write('${ast.int_type_name} ${tag_tmp} = ') + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.writeln(';') + g.write(cur_line) + g.write_type_tag_condition(tag_tmp, cmp_op, index_exprs) +} + // infix_expr_is_op generates code for `is` and `!is` fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { mut left_sym := g.table.final_sym(g.unwrap_generic(g.type_resolver.get_type_or_default(node.left, @@ -1143,30 +1224,10 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { } cmp_op := if node.op == .key_is { '==' } else { '!=' } - g.write('(') - if node.left_type.nr_muls() > 1 { - g.write('*'.repeat(node.left_type.nr_muls() - 1)) - } - if is_aggregate { - g.write('${node.left}') - } else if is_orig_sumtype { - g.prevent_sum_type_unwrapping_once = true - g.expr(node.left) - } else { - g.expr(node.left) - } - g.write(')') - if node.left_type.is_ptr() { - g.write('->') - } else { - g.write('.') - } if left_sym.kind == .interface { - g.write('_typ ${cmp_op} ') - // `_Animal_Dog_index` sub_type := match node.right { ast.TypeNode { - g.unwrap_generic(node.right.typ) + right_type } ast.None { ast.idx_to_type(g.table.type_idxs['None__']) @@ -1176,17 +1237,57 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { } } - sub_sym := g.table.sym(sub_type) - g.write('_${left_sym.cname}_${sub_sym.cname}_index') + g.write_is_type_tag_condition(node, is_aggregate, is_orig_sumtype, cmp_op, g.matching_interface_variant_index_exprs(left_sym, + sub_type)) return } else if left_sym.kind == .sum_type || is_aggregate { - g.write('_typ ${cmp_op} ') + mut aggregate_parent_type := node.left_type + if is_aggregate { + aggregate_sym := g.table.sym(node.left_type) + if aggregate_sym.info is ast.Aggregate { + aggregate_parent_type = aggregate_sym.info.sum_type + } + } + sumtype_parent_type := if is_orig_sumtype && node.left is ast.Ident + && node.left.obj is ast.Var { + (node.left.obj as ast.Var).orig_type + } else if is_aggregate { + aggregate_parent_type + } else { + node.left_type + } + if node.right is ast.None { + g.write_is_type_tag_condition(node, is_aggregate, is_orig_sumtype, cmp_op, [ + '${ast.none_type.idx()}', + ]) + } else if node.right is ast.Ident && node.right.name == g.comptime.comptime_for_variant_var { + mut variant_idx := g.type_resolver.get_ct_type_or_default('${g.comptime.comptime_for_variant_var}.typ', + ast.void_type) + if (left_sym.kind == .sum_type || is_aggregate) && node.left_type.nr_muls() > 0 + && variant_idx.nr_muls() <= node.left_type.nr_muls() { + variant_idx = variant_idx.set_nr_muls(0) + } + g.write_is_type_tag_condition(node, is_aggregate, is_orig_sumtype, cmp_op, g.matching_sumtype_variant_type_idx_exprs(sumtype_parent_type, + variant_idx)) + } else if node.right is ast.TypeNode { + g.write_is_type_tag_condition(node, is_aggregate, is_orig_sumtype, cmp_op, g.matching_sumtype_variant_type_idx_exprs(sumtype_parent_type, + right_type)) + } else { + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write(' ${cmp_op} ') + g.expr(node.right) + } + return } if node.right is ast.None { + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write(' ${cmp_op} ') g.write('${ast.none_type.idx()}') } else if node.right is ast.Ident && node.right.name == g.comptime.comptime_for_variant_var { variant_idx := g.type_resolver.get_ct_type_or_default('${g.comptime.comptime_for_variant_var}.typ', ast.void_type) + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write(' ${cmp_op} ') if (left_sym.kind == .sum_type || is_aggregate) && node.left_type.nr_muls() > 0 && variant_idx.nr_muls() <= node.left_type.nr_muls() { g.write('${int(variant_idx.set_nr_muls(0))}') @@ -1194,8 +1295,12 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { g.write('${int(variant_idx)}') } } else if node.right is ast.TypeNode { + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write(' ${cmp_op} ') g.write('${int(right_type)}') } else { + g.write_type_tag_expr_for_is_left(node, is_aggregate, is_orig_sumtype) + g.write(' ${cmp_op} ') g.expr(node.right) } } diff --git a/vlib/v/gen/c/match.v b/vlib/v/gen/c/match.v index 90a92d4db..dd95620e5 100644 --- a/vlib/v/gen/c/match.v +++ b/vlib/v/gen/c/match.v @@ -237,28 +237,30 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str g.write_v_source_line_info(branch) g.write('if (') } - need_deref := node.cond_type.nr_muls() > 1 - if need_deref { - g.write2('(', '*'.repeat(node.cond_type.nr_muls() - 1)) - } - g.write(cond_var) - if need_deref { - g.write(')') - } cur_expr := unsafe { &branch.exprs[sumtype_index] } + cond_expr := if node.cond_type.nr_muls() > 1 { + '(${'*'.repeat(node.cond_type.nr_muls() - 1)}${cond_var})' + } else { + cond_var + } + tag_expr := '${cond_expr}${dot_or_ptr}_typ' if cond_sym.kind == .sum_type { - g.write('${dot_or_ptr}_typ == ') if cur_expr is ast.None { - g.write('${ast.none_type.idx()} /* none */') + g.write('${tag_expr} == ${ast.none_type.idx()} /* none */') + } else if cur_expr is ast.TypeNode { + variant_type := g.unwrap_generic(g.recheck_concrete_type(cur_expr.typ)) + g.write_type_tag_condition(tag_expr, '==', g.matching_sumtype_variant_type_idx_exprs(node.cond_type, + variant_type)) } else { + g.write('${tag_expr} == ') g.expr(cur_expr) } } else if cond_sym.kind == .interface { if cur_expr is ast.TypeNode { - branch_sym := g.table.sym(g.unwrap_generic(cur_expr.typ)) - g.write('${dot_or_ptr}_typ == _${cond_sym.cname}_${branch_sym.cname}_index') + g.write_type_tag_condition(tag_expr, '==', g.matching_interface_variant_index_exprs(cond_sym, + cur_expr.typ)) } else if cur_expr is ast.None && cond_sym.idx == ast.error_type_idx { - g.write('${dot_or_ptr}_typ == _IError_None___index') + g.write('${tag_expr} == _IError_None___index') } } if use_ternary { diff --git a/vlib/v/gen/c/testdata/msvc_return_alias_is.c.must_have b/vlib/v/gen/c/testdata/msvc_return_alias_is.c.must_have new file mode 100644 index 000000000..38b596ba4 --- /dev/null +++ b/vlib/v/gen/c/testdata/msvc_return_alias_is.c.must_have @@ -0,0 +1,3 @@ +bool main__returns_alias_check(main__MsvcAliasTagSum v) { +int _t +return _t diff --git a/vlib/v/gen/c/testdata/msvc_return_alias_is.vv b/vlib/v/gen/c/testdata/msvc_return_alias_is.vv new file mode 100644 index 000000000..9e7d6acbb --- /dev/null +++ b/vlib/v/gen/c/testdata/msvc_return_alias_is.vv @@ -0,0 +1,14 @@ +// vtest vflags: -cc msvc -os windows + +struct MsvcAliasTagNull {} + +type MsvcAliasTagNullAlias = MsvcAliasTagNull +type MsvcAliasTagSum = MsvcAliasTagNull | MsvcAliasTagNullAlias | int + +fn returns_alias_check(v MsvcAliasTagSum) bool { + return v is MsvcAliasTagNullAlias +} + +fn main() { + _ = returns_alias_check(MsvcAliasTagSum(MsvcAliasTagNull{})) +} diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 7dd1780b3..4ac9e544f 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -522,6 +522,132 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { return resolved } +fn (mut g Gen) alias_equivalent_type(typ ast.Type) ast.Type { + return g.table.fully_unaliased_type(g.unwrap_generic(typ)) +} + +fn (mut g Gen) type_idx_exprs_for_types(types []ast.Type) []string { + mut matches := []string{} + mut seen := map[string]bool{} + for typ in types { + index_expr := g.type_sidx(typ) + if index_expr !in seen { + seen[index_expr] = true + matches << index_expr + } + } + return matches +} + +fn (mut g Gen) matching_sumtype_variant_types(parent_type ast.Type, target_type ast.Type) []ast.Type { + variants := g.sumtype_runtime_variants(parent_type) + target_unaliased := g.alias_equivalent_type(target_type) + mut matches := []ast.Type{} + mut seen := map[string]bool{} + for variant in variants { + if g.alias_equivalent_type(variant) == target_unaliased { + index_expr := g.type_sidx(variant) + if index_expr !in seen { + seen[index_expr] = true + matches << variant + } + } + } + if matches.len > 0 { + return matches + } + for variant in variants { + if g.is_exact_sumtype_variant_match(variant, target_type) { + return [variant] + } + } + return [target_type] +} + +fn (mut g Gen) matching_sumtype_variant_type_idx_exprs(parent_type ast.Type, target_type ast.Type) []string { + return g.type_idx_exprs_for_types(g.matching_sumtype_variant_types(parent_type, target_type)) +} + +fn (mut g Gen) matching_interface_variant_types(interface_sym ast.TypeSymbol, target_type ast.Type) []ast.Type { + if interface_sym.info !is ast.Interface { + return [] + } + target_unaliased := g.alias_equivalent_type(target_type) + info := interface_sym.info as ast.Interface + mut matches := []ast.Type{} + mut seen := map[string]bool{} + for variant in g.runtime_interface_variants(info) { + variant_sym := g.table.sym(variant) + if variant_sym.kind in [.interface, .aggregate] { + continue + } + if variant_sym.info is ast.Struct && variant_sym.info.is_unresolved_generic() { + continue + } + if g.alias_equivalent_type(variant) != target_unaliased { + continue + } + index_expr := g.type_sidx(variant) + if index_expr !in seen { + seen[index_expr] = true + matches << variant + } + } + if matches.len > 0 { + return matches + } + return [target_type] +} + +fn (mut g Gen) matching_interface_variant_index_exprs(interface_sym ast.TypeSymbol, target_type ast.Type) []string { + if interface_sym.info !is ast.Interface { + return [] + } + matching_variants := g.matching_interface_variant_types(interface_sym, target_type) + mut matches := []string{} + mut seen := map[string]bool{} + for variant in matching_variants { + cctype := g.cc_type(ast.mktyp(variant), true) + index_expr := '_${interface_sym.cname}_${cctype}_index' + if index_expr !in seen { + seen[index_expr] = true + matches << index_expr + } + } + if matches.len > 0 { + return matches + } + cctype := g.cc_type(ast.mktyp(target_type), true) + return ['_${interface_sym.cname}_${cctype}_index'] +} + +fn (mut g Gen) matching_interface_variant_type_idx_exprs(interface_sym ast.TypeSymbol, target_type ast.Type) []string { + if interface_sym.info !is ast.Interface { + return [] + } + return g.type_idx_exprs_for_types(g.matching_interface_variant_types(interface_sym, target_type)) +} + +fn (mut g Gen) write_type_tag_condition(tag_expr string, cmp_op string, index_exprs []string) { + if index_exprs.len == 0 { + g.write(if cmp_op == '==' { 'false' } else { 'true' }) + return + } + if index_exprs.len > 1 { + g.write('(') + } + joiner := if cmp_op == '==' { ' || ' } else { ' && ' } + for i, index_expr in index_exprs { + if i > 0 { + g.write(joiner) + } + g.write('${tag_expr} ${cmp_op} ${index_expr}') + } + if index_exprs.len > 1 { + g.write(')') + } +} + fn (mut g Gen) resolved_scope_var_type_uncached(expr ast.Ident) ast.Type { mut scope := if expr.scope != unsafe { nil } { expr.scope.innermost(expr.pos.pos) diff --git a/vlib/v/tests/aliases/alias_is_match_runtime_tag_test.v b/vlib/v/tests/aliases/alias_is_match_runtime_tag_test.v new file mode 100644 index 000000000..9e9923894 --- /dev/null +++ b/vlib/v/tests/aliases/alias_is_match_runtime_tag_test.v @@ -0,0 +1,201 @@ +@[has_globals] +module main + +__global ( + alias_tag_side_effect_calls int +) + +interface AliasTagValue {} + +struct AliasTagNull {} + +struct AliasTagOther {} + +type AliasTagNullAlias = AliasTagNull + +fn alias_tag_get_null() AliasTagValue { + return AliasTagValue(AliasTagNull{}) +} + +fn alias_tag_unwrap_null(v AliasTagValue) AliasTagNull { + match v { + AliasTagNull { + return v + } + else { + panic('expected AliasTagNull') + } + } +} + +fn test_interface_is_matches_alias_to_implementor() { + v := alias_tag_get_null() + assert v is AliasTagNullAlias +} + +fn alias_tag_get_other_with_side_effect() AliasTagValue { + unsafe { + alias_tag_side_effect_calls++ + } + return AliasTagValue(AliasTagOther{}) +} + +fn alias_tag_zero_index_with_side_effect() int { + unsafe { + alias_tag_side_effect_calls++ + } + return 0 +} + +fn test_interface_is_alias_operand_is_evaluated_once() { + unsafe { + alias_tag_side_effect_calls = 0 + } + assert alias_tag_get_other_with_side_effect() !is AliasTagNullAlias + assert alias_tag_side_effect_calls == 1 +} + +fn test_interface_is_alias_indexed_lvalue_is_evaluated_once() { + unsafe { + alias_tag_side_effect_calls = 0 + } + values := [AliasTagValue(AliasTagOther{})] + assert values[alias_tag_zero_index_with_side_effect()] !is AliasTagNullAlias + assert alias_tag_side_effect_calls == 1 +} + +fn test_interface_match_matches_alias_boxed_value() { + v := AliasTagNullAlias{} + assert alias_tag_unwrap_null(v) == AliasTagNull{} +} + +fn test_interface_as_original_matches_alias_boxed_value() { + v := AliasTagValue(AliasTagNullAlias{}) + got := v as AliasTagNull + assert got == AliasTagNull{} +} + +fn test_interface_as_alias_matches_original_boxed_value() { + v := AliasTagValue(AliasTagNull{}) + got := v as AliasTagNullAlias + assert got == AliasTagNullAlias{} +} + +interface AliasTagSingleValue {} + +struct AliasTagSingleNull {} + +type AliasTagSingleNullAlias = AliasTagSingleNull + +fn test_interface_as_alias_matches_single_original_boxed_value() { + v := AliasTagSingleValue(AliasTagSingleNull{}) + assert v is AliasTagSingleNullAlias + got := v as AliasTagSingleNullAlias + assert got == AliasTagSingleNullAlias{} +} + +type AliasTagSum = AliasTagNull | int +type AliasTagSumWithAlias = AliasTagNull | AliasTagNullAlias | int + +fn alias_tag_get_sum_null() AliasTagSum { + return AliasTagNull{} +} + +fn test_sumtype_is_matches_alias_to_variant() { + v := alias_tag_get_sum_null() + assert v is AliasTagNullAlias +} + +fn test_sumtype_match_matches_alias_boxed_value() { + v := AliasTagSum(AliasTagNullAlias{}) + match v { + AliasTagNull { + assert true + } + else { + assert false + } + } +} + +fn test_sumtype_as_alias_matches_single_original_variant_tag() { + v := AliasTagSum(AliasTagNull{}) + assert v is AliasTagNullAlias + got := v as AliasTagNullAlias + assert got == AliasTagNullAlias{} +} + +fn test_sumtype_is_alias_matches_original_variant_tag() { + v := AliasTagSumWithAlias(AliasTagNull{}) + assert v is AliasTagNullAlias +} + +fn test_sumtype_match_alias_matches_original_variant_tag() { + v := AliasTagSumWithAlias(AliasTagNull{}) + match v { + AliasTagNullAlias { + assert true + } + else { + assert false + } + } +} + +fn test_sumtype_is_original_matches_alias_variant_tag() { + v := AliasTagSumWithAlias(AliasTagNullAlias{}) + assert v is AliasTagNull +} + +fn test_sumtype_match_original_matches_alias_variant_tag() { + v := AliasTagSumWithAlias(AliasTagNullAlias{}) + match v { + AliasTagNull { + assert true + } + else { + assert false + } + } +} + +fn test_sumtype_as_original_matches_alias_variant_tag() { + v := AliasTagSumWithAlias(AliasTagNullAlias{}) + got := v as AliasTagNull + assert got == AliasTagNull{} +} + +fn test_sumtype_as_alias_matches_original_variant_tag() { + v := AliasTagSumWithAlias(AliasTagNull{}) + got := v as AliasTagNullAlias + assert got == AliasTagNullAlias{} +} + +fn aggregate_branch_is_original(v AliasTagSumWithAlias) bool { + match v { + AliasTagNull, int { + return v is AliasTagNull + } + else { + return false + } + } +} + +fn aggregate_branch_is_alias(v AliasTagSumWithAlias) bool { + match v { + AliasTagNull, int { + return v is AliasTagNullAlias + } + else { + return false + } + } +} + +fn test_sumtype_is_in_aggregate_branch_matches_alias_tags() { + assert aggregate_branch_is_original(AliasTagSumWithAlias(AliasTagNullAlias{})) + assert aggregate_branch_is_alias(AliasTagSumWithAlias(AliasTagNullAlias{})) + assert !aggregate_branch_is_original(AliasTagSumWithAlias(1)) + assert !aggregate_branch_is_alias(AliasTagSumWithAlias(1)) +} diff --git a/vlib/v/tests/generics/generic_sumtype_str_test.v b/vlib/v/tests/generics/generic_sumtype_str_test.v index ca720629c..99e23510e 100644 --- a/vlib/v/tests/generics/generic_sumtype_str_test.v +++ b/vlib/v/tests/generics/generic_sumtype_str_test.v @@ -31,6 +31,17 @@ pub fn (m &Maybe[T]) as_ref[T]() Maybe[&T] { } } +fn generic_match_has_value[T](m Maybe[T]) bool { + return match m { + T { + true + } + else { + false + } + } +} + fn test_generic_sumtype_str() { a := some(123) b := some('abc') @@ -60,3 +71,8 @@ fn test_generic_sumtype_str_with_ref_variant() { assert sum_ref.str() == 'Some(123)' assert '${sum_ref}' == 'Some(123)' } + +fn test_generic_sumtype_match_resolves_variant_type() { + assert generic_match_has_value(some(123)) + assert !generic_match_has_value(noth[int]()) +} diff --git a/vlib/v/tests/sumtypes/aggregate_is_nodetype_test.v b/vlib/v/tests/sumtypes/aggregate_is_nodetype_test.v index 62dcd751c..ad22d72f3 100644 --- a/vlib/v/tests/sumtypes/aggregate_is_nodetype_test.v +++ b/vlib/v/tests/sumtypes/aggregate_is_nodetype_test.v @@ -17,3 +17,20 @@ fn test_aggregate_is_nodetype() { } } } + +fn is_aggregate_branch_string(x Abc) bool { + match x { + string, int { + return x is string + } + else { + return false + } + } +} + +fn test_aggregate_is_nodetype_in_smartcast_branch() { + assert is_aggregate_branch_string(Abc('test')) + assert !is_aggregate_branch_string(Abc(1)) + assert !is_aggregate_branch_string(Abc(false)) +} -- 2.39.5