From 87340bcca2aa7f7cb6b524d6573a08b52ed1401b Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Wed, 11 Feb 2026 11:07:46 +0800 Subject: [PATCH] checker,cgen: fix alias for interface and option types (fix #26551) (#26565) --- vlib/v/checker/checker.v | 2 +- vlib/v/checker/infix.v | 7 +++- vlib/v/gen/c/assign.v | 5 ++- vlib/v/gen/c/auto_eq_methods.v | 4 ++- vlib/v/gen/c/auto_str_methods.v | 27 +++++++++++--- vlib/v/gen/c/cgen.v | 15 ++++++-- vlib/v/gen/c/infix.v | 8 +++-- vlib/v/tests/aliases/alias_interface_test.v | 40 +++++++++++++++++++++ vlib/v/tests/aliases/alias_option_test.v | 15 ++++++++ 9 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 vlib/v/tests/aliases/alias_interface_test.v create mode 100644 vlib/v/tests/aliases/alias_option_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 92d841011..52bfcf689 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1297,7 +1297,7 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to utyp := c.unwrap_generic(typ) styp := c.table.type_to_str(utyp) typ_sym := c.table.sym(utyp) - mut inter_sym := c.table.sym(interface_type) + mut inter_sym := c.table.final_sym(interface_type) if !inter_sym.is_pub && inter_sym.mod !in [typ_sym.mod, c.mod] && typ_sym.mod != 'builtin' { c.error('`${styp}` cannot implement private interface `${inter_sym.name}` of other module', pos) diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index 480681429..6e245312d 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -879,8 +879,11 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { node.promoted_type = return_type return return_type } - left_is_option := left_type.has_flag(.option) + left_is_option := left_type.has_flag(.option) || (left_sym.kind == .alias + && (left_sym.info as ast.Alias).parent_type.has_flag(.option)) right_is_option := right_type.has_flag(.option) + || (right_sym.kind == .alias + && (right_sym.info as ast.Alias).parent_type.has_flag(.option)) if (node.right is ast.None && left_is_option) || (node.left is ast.None && right_is_option) { return ast.bool_type @@ -1093,7 +1096,9 @@ fn (mut c Checker) check_option_infix_expr(node ast.InfixExpr, left_type ast.Typ return } left_is_option := left_type.has_flag(.option) + || (left_sym.kind == .alias && (left_sym.info as ast.Alias).parent_type.has_flag(.option)) right_is_option := right_type.has_flag(.option) + || (right_sym.kind == .alias && (right_sym.info as ast.Alias).parent_type.has_flag(.option)) if (node.left is ast.None && right_is_option) || (node.right is ast.None && left_is_option) || (left_sym.kind == .none || right_sym.kind == .none) { diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index a06698cd4..6af2b35cf 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -127,7 +127,10 @@ fn (mut g Gen) expr_opt_with_cast(expr ast.Expr, expr_typ ast.Type, ret_typ ast. g.past_tmp_var_done(past) } unwrapped_ret := g.unwrap_generic(ret_typ) - styp := g.base_type(unwrapped_ret) + // Unwrap type aliases to ensure sizeof uses the base type size, not the alias size + // This fixes the ASAN stack-buffer-overflow issue when using type aliases like MaybeInt = ?int + unaliased_ret := g.table.unaliased_type(unwrapped_ret) + styp := g.base_type(unaliased_ret) decl_styp := g.styp(unwrapped_ret).replace('*', '_ptr') g.writeln('${decl_styp} ${past.tmp_var};') is_none := expr is ast.CastExpr && expr.expr is ast.None diff --git a/vlib/v/gen/c/auto_eq_methods.v b/vlib/v/gen/c/auto_eq_methods.v index 2afbc442c..1ca59bd36 100644 --- a/vlib/v/gen/c/auto_eq_methods.v +++ b/vlib/v/gen/c/auto_eq_methods.v @@ -590,7 +590,9 @@ fn (mut g Gen) gen_map_equality_fn(left_type ast.Type) string { } else { if value.typ.has_flag(.option) { - fn_builder.writeln('\t\tif (memcmp(v.data, ((${ptr_value_styp}*)builtin__map_get(${b}, k, &(${ptr_value_styp}[]){ 0 }))->data, sizeof(${g.base_type(value.typ)})) != 0) {') + // For option types, use the unaliased type to get the correct sizeof + unaliased_typ := g.table.unaliased_type(value.typ) + fn_builder.writeln('\t\tif (memcmp(v.data, ((${ptr_value_styp}*)builtin__map_get(${b}, k, &(${ptr_value_styp}[]){ 0 }))->data, sizeof(${g.base_type(unaliased_typ)})) != 0) {') } else { fn_builder.writeln('\t\tif (*(${ptr_value_styp}*)builtin__map_get(${b}, k, &(${ptr_value_styp}[]){ 0 }) != v) {') } diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 0b9a4de68..1b209effe 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -119,7 +119,26 @@ fn (mut g Gen) final_gen_str(typ StrType) { } if typ.typ.has_flag(.option) { opt_typ := if typ.typ.has_flag(.option_mut_param_t) { styp.replace('*', '') } else { styp } - g.gen_str_for_option(typ.typ, opt_typ, str_fn_name) + // Check if this is a type alias to an option type + mut type_name := 'Option' + mut unwrapped_typ := g.table.unaliased_type(typ.typ) + if unwrapped_typ != typ.typ { + // This is a type alias, check if it's an alias to an option type + alias_sym := g.table.sym(typ.typ) + if alias_sym.kind == .alias && alias_sym.info is ast.Alias { + // Check if the parent type has the option flag + if alias_sym.info.parent_type.has_flag(.option) { + // This is an alias to an option type (e.g., type MyOpt = ?MyStruct) + // Use the alias name instead of "Option" + mut alias_name := alias_sym.name + if alias_name.contains('.') { + alias_name = alias_name.all_after_last('.') + } + type_name = '?${alias_name}' + } + } + } + g.gen_str_for_option(typ.typ, opt_typ, str_fn_name, type_name) return } if typ.typ.has_flag(.result) { @@ -177,7 +196,7 @@ fn (mut g Gen) final_gen_str(typ StrType) { } } -fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) { +fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string, type_name string) { $if trace_autostr ? { eprintln('> gen_str_for_option: ${typ.debug()} | ${styp} | ${str_fn_name}') } @@ -224,9 +243,9 @@ fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}(${deref}it.data);') } } - g.auto_str_funcs.writeln('\t\treturn ${str_intp_sub('Option(%%)', 'res')};') + g.auto_str_funcs.writeln('\t\treturn ${str_intp_sub('${type_name}(%%)', 'res')};') g.auto_str_funcs.writeln('\t}') - g.auto_str_funcs.writeln('\treturn _S("Option(none)");') + g.auto_str_funcs.writeln('\treturn _S("${type_name}(none)");') g.auto_str_funcs.writeln('}') } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 38b2cf455..831eadf05 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1380,6 +1380,11 @@ fn (mut g Gen) option_type_name(t ast.Type) (string, string) { mut base := g.base_type(t) mut styp := '' sym := g.table.sym(t) + // If this is a type alias to an option type, use the parent type's option name + // This ensures that ?int and MaybeInt (where MaybeInt = ?int) generate the same C type + if sym.kind == .alias && sym.info is ast.Alias && sym.info.parent_type.has_flag(.option) { + return g.option_type_name(sym.info.parent_type) + } if sym.info is ast.FnType { base = 'anon_fn_${g.table.fn_type_signature(sym.info.func)}' } @@ -1396,12 +1401,16 @@ fn (mut g Gen) option_type_name(t ast.Type) (string, string) { fn (mut g Gen) result_type_name(t ast.Type) (string, string) { mut base := g.base_type(t) + mut styp := '' + sym := g.table.sym(t) + // If this is a type alias to a result type, use the parent type's result name + if sym.kind == .alias && sym.info is ast.Alias && sym.info.parent_type.has_flag(.result) { + return g.result_type_name(sym.info.parent_type) + } if t.has_flag(.option) { g.register_option(t) base = '_option_' + base } - mut styp := '' - sym := g.table.sym(t) if sym.info is ast.FnType { base = 'anon_fn_${g.table.fn_type_signature(sym.info.func)}' } @@ -3182,7 +3191,7 @@ fn (mut g Gen) expr_with_fixed_array(expr ast.Expr, got_type_raw ast.Type, expec // use instead of expr() when you need to cast to a different type fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_type ast.Type) { got_type := ast.mktyp(got_type_raw) - exp_sym := g.table.sym(expected_type) + exp_sym := g.table.final_sym(expected_type) got_sym := g.table.sym(got_type) expected_is_ptr := expected_type.is_ptr() got_is_ptr := got_type.is_ptr() diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 08170fd13..f1de18d5b 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -112,8 +112,12 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { g.gen_plain_infix_expr(node) return } - left_is_option := left_type.has_flag(.option) - right_is_option := right_type.has_flag(.option) + left_sym := g.table.sym(left_type) + right_sym := g.table.sym(right_type) + left_is_option := left_type.has_flag(.option) || (left_sym.kind == .alias + && left_sym.info is ast.Alias && left_sym.info.parent_type.has_flag(.option)) + right_is_option := right_type.has_flag(.option) || (right_sym.kind == .alias + && right_sym.info is ast.Alias && right_sym.info.parent_type.has_flag(.option)) is_none_check := left_is_option && node.right is ast.None if is_none_check { g.gen_is_none_check(node) diff --git a/vlib/v/tests/aliases/alias_interface_test.v b/vlib/v/tests/aliases/alias_interface_test.v new file mode 100644 index 000000000..89f4a0660 --- /dev/null +++ b/vlib/v/tests/aliases/alias_interface_test.v @@ -0,0 +1,40 @@ +module main + +// Define a simple interface +pub interface Node { + kind() int + name() string +} + +// Implement the interface +pub struct MyNode { +pub: + value int + text string +} + +pub fn (n MyNode) kind() int { + return 1 +} + +pub fn (n MyNode) name() string { + return n.text +} + +// Define a type alias to the interface +pub type Expr = Node + +// Function using the type alias +pub fn process_node(expr Expr) string { + return expr.name() +} + +fn test_alias_interface() { + node := MyNode{ + value: 42 + text: 'test' + } + result := process_node(node) + println('Result: ${result}') + assert result == 'test' +} diff --git a/vlib/v/tests/aliases/alias_option_test.v b/vlib/v/tests/aliases/alias_option_test.v new file mode 100644 index 000000000..34690e1b0 --- /dev/null +++ b/vlib/v/tests/aliases/alias_option_test.v @@ -0,0 +1,15 @@ +module main + +// Test option type alias +pub type MaybeInt = ?int + +pub fn is_some(val MaybeInt) bool { + return val != none +} + +fn test_alias_option() { + v := ?int(42) + result := is_some(v) + println('Has value: ${result}') + assert result +} -- 2.39.5