# HG changeset patch # User Bram Moolenaar # Date 1672692302 -3600 # Node ID 9b13b3a63bc0f4dd95474ee4415ea77478102a8b # Parent 2b8fe38a998955a291c4e367ff92ba7e723e09be patch 9.0.1134: comparing objects uses identity instead of equality Commit: https://github.com/vim/vim/commit/bcf31ec36b4b056bf06d21036640c6f0235e9c2b Author: Bram Moolenaar Date: Mon Jan 2 20:32:24 2023 +0000 patch 9.0.1134: comparing objects uses identity instead of equality Problem: Comparing objects uses identity instead of equality. Solution: Compare the object values. diff --git a/src/proto/typval.pro b/src/proto/typval.pro --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -63,6 +63,8 @@ int typval_compare(typval_T *tv1, typval int typval_compare_list(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res); int typval_compare_null(typval_T *tv1, typval_T *tv2); int typval_compare_blob(typval_T *tv1, typval_T *tv2, exprtype_T type, int *res); +int typval_compare_class(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res); +int typval_compare_object(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res); int typval_compare_dict(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res); int typval_compare_func(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res); int typval_compare_string(typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res); diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -367,6 +367,50 @@ def Test_class_object_member_access() v9.CheckScriptFailure(lines, 'E1041:') enddef +def Test_class_object_compare() + var class_lines =<< trim END + vim9script + class Item + this.nr = 0 + this.name = 'xx' + endclass + END + + # used at the script level and in a compiled function + var test_lines =<< trim END + var i1 = Item.new() + assert_equal(i1, i1) + assert_true(i1 is i1) + var i2 = Item.new() + assert_equal(i1, i2) + assert_false(i1 is i2) + var i3 = Item.new(0, 'xx') + assert_equal(i1, i3) + + var io1 = Item.new(1, 'xx') + assert_notequal(i1, io1) + var io2 = Item.new(0, 'yy') + assert_notequal(i1, io2) + END + + v9.CheckScriptSuccess(class_lines + test_lines) + # TODO: this does not work yet + #v9.CheckScriptSuccess( + # class_lines + ['def Test()'] + test_lines + ['enddef', 'Test()']) + + for op in ['>', '>=', '<', '<=', '=~', '!~'] + var op_lines = [ + 'var i1 = Item.new()', + 'var i2 = Item.new()', + 'echo i1 ' .. op .. ' i2', + ] + v9.CheckScriptFailure(class_lines + op_lines, 'E1153: Invalid operation for object') + # TODO: this does not work yet + #v9.CheckScriptFailure(class_lines + # + ['def Test()'] + op_lines + ['enddef', 'Test()'], 'E99:') + endfor +enddef + def Test_class_member() # check access rules var lines =<< trim END diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -1310,6 +1310,24 @@ typval_compare( } n1 = res; } + else if (tv1->v_type == VAR_CLASS || tv2->v_type == VAR_CLASS) + { + if (typval_compare_class(tv1, tv2, type, ic, &res) == FAIL) + { + clear_tv(tv1); + return FAIL; + } + n1 = res; + } + else if (tv1->v_type == VAR_OBJECT || tv2->v_type == VAR_OBJECT) + { + if (typval_compare_object(tv1, tv2, type, ic, &res) == FAIL) + { + clear_tv(tv1); + return FAIL; + } + n1 = res; + } else if (tv1->v_type == VAR_DICT || tv2->v_type == VAR_DICT) { if (typval_compare_dict(tv1, tv2, type, ic, &res) == FAIL) @@ -1580,6 +1598,77 @@ typval_compare_blob( } /* + * Compare "tv1" to "tv2" as classes according to "type". + * Put the result, false or true, in "res". + * Return FAIL and give an error message when the comparison can't be done. + */ + int +typval_compare_class( + typval_T *tv1, + typval_T *tv2, + exprtype_T type UNUSED, + int ic UNUSED, + int *res) +{ + // TODO: use "type" + *res = tv1->vval.v_class == tv2->vval.v_class; + return OK; +} + +/* + * Compare "tv1" to "tv2" as objects according to "type". + * Put the result, false or true, in "res". + * Return FAIL and give an error message when the comparison can't be done. + */ + int +typval_compare_object( + typval_T *tv1, + typval_T *tv2, + exprtype_T type, + int ic, + int *res) +{ + int res_match = type == EXPR_EQUAL || type == EXPR_IS ? TRUE : FALSE; + + if (tv1->vval.v_object == NULL && tv2->vval.v_object == NULL) + { + *res = res_match; + return OK; + } + if (tv1->vval.v_object == NULL || tv2->vval.v_object == NULL) + { + *res = !res_match; + return OK; + } + + class_T *cl1 = tv1->vval.v_object->obj_class; + class_T *cl2 = tv2->vval.v_object->obj_class; + if (cl1 != cl2 || cl1 == NULL || cl2 == NULL) + { + *res = !res_match; + return OK; + } + + object_T *obj1 = tv1->vval.v_object; + object_T *obj2 = tv2->vval.v_object; + if (type == EXPR_IS || type == EXPR_ISNOT) + { + *res = obj1 == obj2 ? res_match : !res_match; + return OK; + } + + for (int i = 0; i < cl1->class_obj_member_count; ++i) + if (!tv_equal((typval_T *)(obj1 + 1) + i, + (typval_T *)(obj2 + 1) + i, ic, TRUE)) + { + *res = !res_match; + return OK; + } + *res = res_match; + return OK; +} + +/* * Compare "tv1" to "tv2" as dictionaries according to "type" and "ic". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. @@ -1920,11 +2009,12 @@ tv_equal( return tv1->vval.v_instr == tv2->vval.v_instr; case VAR_CLASS: + // A class only exists once, equality is identity. return tv1->vval.v_class == tv2->vval.v_class; case VAR_OBJECT: - // TODO: compare values - return tv1->vval.v_object == tv2->vval.v_object; + (void)typval_compare_object(tv1, tv2, EXPR_EQUAL, ic, &r); + return r; case VAR_PARTIAL: return tv1->vval.v_partial == tv2->vval.v_partial; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1134, +/**/ 1133, /**/ 1132, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -164,6 +164,8 @@ typedef enum { ISN_COMPAREDICT, ISN_COMPAREFUNC, ISN_COMPAREANY, + ISN_COMPARECLASS, + ISN_COMPAREOBJECT, // expression operations ISN_CONCAT, // concatenate isn_arg.number strings diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -4697,6 +4697,8 @@ exec_instructions(ectx_T *ectx) case ISN_COMPAREFUNC: case ISN_COMPARESTRING: case ISN_COMPAREBLOB: + case ISN_COMPARECLASS: + case ISN_COMPAREOBJECT: { typval_T *tv1 = STACK_TV_BOT(-2); typval_T *tv2 = STACK_TV_BOT(-1); @@ -4726,10 +4728,19 @@ exec_instructions(ectx_T *ectx) status = typval_compare_string(tv1, tv2, exprtype, ic, &res); } - else + else if (iptr->isn_type == ISN_COMPAREBLOB) { status = typval_compare_blob(tv1, tv2, exprtype, &res); } + else if (iptr->isn_type == ISN_COMPARECLASS) + { + status = typval_compare_class(tv1, tv2, exprtype, &res); + } + else // ISN_COMPAREOBJECT + { + status = typval_compare_object(tv1, tv2, + exprtype, &res); + } --ectx->ec_stack.ga_len; clear_tv(tv1); clear_tv(tv2); @@ -6807,6 +6818,8 @@ list_instructions(char *pfx, isn_T *inst case ISN_COMPARELIST: case ISN_COMPAREDICT: case ISN_COMPAREFUNC: + case ISN_COMPARECLASS: + case ISN_COMPAREOBJECT: case ISN_COMPAREANY: { char *p; @@ -6844,6 +6857,9 @@ list_instructions(char *pfx, isn_T *inst case ISN_COMPARELIST: type = "COMPARELIST"; break; case ISN_COMPAREDICT: type = "COMPAREDICT"; break; case ISN_COMPAREFUNC: type = "COMPAREFUNC"; break; + case ISN_COMPARECLASS: type = "COMPARECLASS"; break; + case ISN_COMPAREOBJECT: + type = "COMPAREOBJECT"; break; case ISN_COMPAREANY: type = "COMPAREANY"; break; default: type = "???"; break; } diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -273,7 +273,8 @@ compile_class_object_index(cctx_T *cctx, class_T *cl = (class_T *)type->tt_member; if (*name_end == '(') { - // TODO + // TODO: method or function call + emsg("compile_class_object_index(): object/class call not handled yet"); } else if (type->tt_type == VAR_OBJECT) { @@ -300,7 +301,7 @@ compile_class_object_index(cctx_T *cctx, else { // TODO: class member - emsg("compile_class_object_index(): not handled"); + emsg("compile_class_object_index(): class member not handled yet"); } return FAIL; diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -254,11 +254,11 @@ check_number_or_float(vartype_T type1, v */ int generate_add_instr( - cctx_T *cctx, - vartype_T vartype, - type_T *type1, - type_T *type2, - exprtype_T expr_type) + cctx_T *cctx, + vartype_T vartype, + type_T *type1, + type_T *type2, + exprtype_T expr_type) { isn_T *isn = generate_instr_drop(cctx, vartype == VAR_NUMBER ? ISN_OPNR @@ -416,6 +416,8 @@ get_compare_isn( case VAR_LIST: isntype = ISN_COMPARELIST; break; case VAR_DICT: isntype = ISN_COMPAREDICT; break; case VAR_FUNC: isntype = ISN_COMPAREFUNC; break; + case VAR_CLASS: isntype = ISN_COMPARECLASS; break; + case VAR_OBJECT: isntype = ISN_COMPAREOBJECT; break; default: isntype = ISN_COMPAREANY; break; } } @@ -455,6 +457,13 @@ get_compare_isn( exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(vartype1)); return ISN_DROP; } + if (!(exprtype == EXPR_IS || exprtype == EXPR_ISNOT + || exprtype == EXPR_EQUAL || exprtype == EXPR_NEQUAL) + && (isntype == ISN_COMPAREOBJECT || isntype == ISN_COMPARECLASS)) + { + semsg(_(e_invalid_operation_for_str), vartype_name(vartype1)); + return ISN_DROP; + } if (isntype == ISN_DROP || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL && (vartype1 == VAR_BOOL || vartype1 == VAR_SPECIAL @@ -2512,12 +2521,14 @@ delete_instr(isn_T *isn) case ISN_COMPAREANY: case ISN_COMPAREBLOB: case ISN_COMPAREBOOL: + case ISN_COMPARECLASS: case ISN_COMPAREDICT: case ISN_COMPAREFLOAT: case ISN_COMPAREFUNC: case ISN_COMPARELIST: case ISN_COMPARENR: case ISN_COMPARENULL: + case ISN_COMPAREOBJECT: case ISN_COMPARESPECIAL: case ISN_COMPARESTRING: case ISN_CONCAT: