From 45ffbccabad9928f39cab7f767d3f1c3865e6971 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 19 Jun 2026 22:32:10 +0300 Subject: [PATCH] cgen: fix comparing two option-of-struct values (`?Struct == ?Struct`) (#27496) --- vlib/v/gen/c/infix.v | 75 +++++++++++---- vlib/v/tests/option_struct_eq_test.v | 131 +++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 18 deletions(-) create mode 100644 vlib/v/tests/option_struct_eq_test.v diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 2f35cf2c7..91b1c0a9f 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -426,28 +426,67 @@ fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { g.write(')') } .struct { - ptr_typ := g.equality_fn(left.unaliased) - if left.typ.is_ptr() || right.typ.is_ptr() { - // `&lvalue` on either side means the user is comparing addresses; skip the deep `_struct_eq` (`&StructInit{}` still does deep eq). - left_is_addr_of_lvalue := node.left is ast.PrefixExpr && node.left.op == .amp - && node.left.right.is_lvalue() - right_is_addr_of_lvalue := node.right is ast.PrefixExpr && node.right.op == .amp - && node.right.right.is_lvalue() - if left.typ.is_ptr() && right.typ.is_ptr() - && (left_is_addr_of_lvalue || right_is_addr_of_lvalue) { - g.gen_plain_infix_expr(node) + if left_is_option && right_is_option { + bare_typ := g.equality_fn(left.unaliased.clear_flag(.option).set_nr_muls(0)) + old_inside_opt_or_res := g.inside_opt_or_res + g.inside_opt_or_res = true + inside_and_rhs := g.infix_left_var_name.len > 0 + mut lv := '' + mut rv := '' + if inside_and_rhs { + lv = g.expr_string(node.left) + rv = g.expr_string(node.right) + } else { + left_tmp := g.expr_to_ctemp_before_stmt(node.left, left_type) + right_tmp := g.expr_to_ctemp_before_stmt(node.right, right_type) + lv = left_tmp.name + rv = right_tmp.name + } + if node.op == .eq { + g.write('(') } else { - g.gen_struct_pointer_eq_op(node, left_type, right_type, ptr_typ) + g.write('!(') } + g.write('(${lv}.state == 2 && ${rv}.state == 2) || ') + g.write('(${lv}.state == ${rv}.state && ${lv}.state != 2 && ') + if left.typ.is_ptr() { + ptr_styp := g.styp(left.unaliased.clear_flag(.option)) + nr_muls := left.typ.nr_muls() + deref := '*'.repeat(nr_muls) + lp := '*(${ptr_styp}*)&${lv}.data' + rp := '*(${ptr_styp}*)&${rv}.data' + g.write('(${lp} == ${rp} || (${lp} != 0 && ${rp} != 0 && ') + g.write('${bare_typ}_struct_eq(${deref}${lp}, ${deref}${rp})') + g.write('))') + } else { + styp := g.base_type(left_type) + g.write('${bare_typ}_struct_eq(*(${styp}*)&${lv}.data, *(${styp}*)&${rv}.data)') + } + g.write('))') + g.inside_opt_or_res = old_inside_opt_or_res } else { - if node.op == .ne { - g.write('!') + ptr_typ := g.equality_fn(left.unaliased) + if left.typ.is_ptr() || right.typ.is_ptr() { + left_is_addr_of_lvalue := node.left is ast.PrefixExpr + && node.left.op == .amp && node.left.right.is_lvalue() + right_is_addr_of_lvalue := node.right is ast.PrefixExpr + && node.right.op == .amp && node.right.right.is_lvalue() + if left.typ.is_ptr() && right.typ.is_ptr() + && (left_is_addr_of_lvalue || right_is_addr_of_lvalue) { + g.gen_plain_infix_expr(node) + } else { + g.gen_struct_pointer_eq_op(node, left_type, right_type, ptr_typ) + } + } else { + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_struct_eq(') + g.expr(node.left) + g.write(', ') + g.expr(node.right) + g.write(')') } - g.write('${ptr_typ}_struct_eq(') - g.expr(node.left) - g.write(', ') - g.expr(node.right) - g.write(')') } } .sum_type { diff --git a/vlib/v/tests/option_struct_eq_test.v b/vlib/v/tests/option_struct_eq_test.v new file mode 100644 index 000000000..da2348733 --- /dev/null +++ b/vlib/v/tests/option_struct_eq_test.v @@ -0,0 +1,131 @@ +struct Id { + v int +} + +struct Person { + name string + age int +} + +fn test_option_struct_ne() { + assert ?Id(Id{ + v: 1 + }) != ?Id(Id{ + v: 2 + }) + assert ?Id(Id{ + v: 1 + }) != ?Id(none) +} + +fn test_option_struct_eq() { + assert ?Id(Id{ + v: 1 + }) == ?Id(Id{ + v: 1 + }) + assert ?Id(none) == ?Id(none) +} + +fn test_option_struct_ne_with_strings() { + assert ?Person(Person{ + name: 'Alice' + age: 30 + }) != ?Person(Person{ + name: 'Bob' + age: 25 + }) + assert ?Person(Person{ + name: 'Alice' + age: 30 + }) != ?Person(none) +} + +fn test_option_struct_eq_with_strings() { + assert ?Person(Person{ + name: 'Alice' + age: 30 + }) == ?Person(Person{ + name: 'Alice' + age: 30 + }) + assert ?Person(none) == ?Person(none) +} + +fn cmp(a ?Id, b ?Id) bool { + return a != b +} + +fn test_option_struct_eq_in_fn() { + assert cmp(Id{ v: 1 }, Id{ + v: 2 + }) == true + assert cmp(Id{ v: 1 }, Id{ + v: 1 + }) == false + assert cmp(none, none) == false + assert cmp(Id{ v: 1 }, none) == true +} + +fn make_id(v int) ?Id { + return Id{ + v: v + } +} + +fn make_none_id() ?Id { + return none +} + +fn test_option_struct_eq_fn_call_result() { + assert make_id(1) == make_id(1) + assert make_id(1) != make_id(2) + assert make_none_id() == make_none_id() + assert make_id(1) != make_none_id() +} + +fn test_option_struct_eq_in_short_circuit() { + a := ?Id(Id{ + v: 1 + }) + b := ?Id(Id{ + v: 1 + }) + c := ?Id(Id{ + v: 2 + }) + assert true && a == b + assert true && a != c + assert false || a == b + assert !(false && a != b) +} + +fn test_option_ptr_struct_eq() { + a := ?&Id(&Id{ + v: 1 + }) + b := ?&Id(&Id{ + v: 1 + }) + c := ?&Id(&Id{ + v: 2 + }) + d := ?&Id(none) + e := ?&Id(none) + assert a == b + assert a != c + assert a != d + assert d == e + assert a != ?&Id(none) +} + +fn test_option_ptr_struct_ne() { + a := ?&Id(&Id{ + v: 1 + }) + b := ?&Id(&Id{ + v: 2 + }) + assert a != b + assert a != ?&Id(none) +} -- 2.39.5