# HG changeset patch # User Bram Moolenaar # Date 1673898304 -3600 # Node ID 48431422f76676a096fe3559b7ea88fe44483885 # Parent 048ae7b3fdd6133549bdc5b9c20599674ba19bc0 patch 9.0.1209: getting interface member does not always work Commit: https://github.com/vim/vim/commit/29ac5df37baf7e6e751c7ebd4ab37a2aa826e9e6 Author: Bram Moolenaar Date: Mon Jan 16 19:43:47 2023 +0000 patch 9.0.1209: getting interface member does not always work Problem: Getting interface member does not always work. Solution: Convert the index on the interface to the index on the object. (closes #11825) diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -1,4 +1,5 @@ /* vim9class.c */ +int object_index_from_itf_index(class_T *itf, int idx, class_T *cl); void ex_class(exarg_T *eap); type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx); void ex_enum(exarg_T *eap); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -5,6 +5,7 @@ isn_T *generate_instr_type(cctx_T *cctx, isn_T *generate_instr_debug(cctx_T *cctx); int generate_CONSTRUCT(cctx_T *cctx, class_T *cl); int generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type); +int generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int itf_idx, type_T *type); int generate_STORE_THIS(cctx_T *cctx, int idx); int may_generate_2STRING(int offset, int tolerant, cctx_T *cctx); int generate_add_instr(cctx_T *cctx, vartype_T vartype, type_T *type1, type_T *type2, exprtype_T expr_type); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1483,6 +1483,14 @@ typedef struct { char_u *ocm_init; // allocated } ocmember_T; +// used for the lookup table of a class member index +typedef struct itf2class_S itf2class_T; +struct itf2class_S { + itf2class_T *i2c_next; + class_T *i2c_class; + // array with ints follows +}; + #define CLASS_INTERFACE 1 // "class_T": used for v_class of typval of VAR_CLASS @@ -1501,6 +1509,7 @@ struct class_S int class_interface_count; char_u **class_interfaces; // allocated array of names class_T **class_interfaces_cl; // interfaces (counts as reference) + itf2class_T *class_itf2class; // member index lookup tables // class members: "static varname" int class_class_member_count; 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 @@ -870,6 +870,32 @@ def Test_class_implements_interface() endclass END v9.CheckScriptFailure(lines, 'E1349: Function "Methods" of interface "Some" not implemented') + + # Check different order of members in class and interface works. + lines =<< trim END + vim9script + + interface Result + this.label: string + this.errpos: number + endinterface + + # order of members is opposite of interface + class Failure implements Result + this.errpos: number = 42 + this.label: string = 'label' + endclass + + def Test() + var result: Result = Failure.new() + + assert_equal('label', result.label) + assert_equal(42, result.errpos) + enddef + + Test() + END + v9.CheckScriptSuccess(lines) enddef def Test_class_used_as_type() 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 */ /**/ + 1209, +/**/ 1208, /**/ 1207, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -34,10 +34,11 @@ typedef enum { ISN_INSTR, // instructions compiled from expression ISN_CONSTRUCT, // construct an object, using contstruct_T ISN_GET_OBJ_MEMBER, // object member, index is isn_arg.number + ISN_GET_ITF_MEMBER, // interface member, index is isn_arg.classmember ISN_STORE_THIS, // store value in "this" object member, index is // isn_arg.number - ISN_LOAD_CLASSMEMBER, // load class member, using classmember_T - ISN_STORE_CLASSMEMBER, // store in class member, using classmember_T + ISN_LOAD_CLASSMEMBER, // load class member, using isn_arg.classmember + ISN_STORE_CLASSMEMBER, // store in class member, using isn_arg.classmember // get and set variables ISN_LOAD, // push local variable isn_arg.number @@ -480,7 +481,7 @@ typedef struct { class_T *construct_class; // class the object is created from } construct_T; -// arguments to ISN_STORE_CLASSMEMBER and ISN_LOAD_CLASSMEMBER +// arguments to ISN_STORE_CLASSMEMBER, ISN_LOAD_CLASSMEMBER, ISN_GET_ITF_MEMBER typedef struct { class_T *cm_class; int cm_idx; diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -197,6 +197,32 @@ add_members_to_class( } /* + * Convert a member index "idx" of interface "itf" to the member index of class + * "cl" implementing that interface. + */ + int +object_index_from_itf_index(class_T *itf, int idx, class_T *cl) +{ + if (idx > itf->class_obj_member_count) + { + siemsg("index %d out of range for interface %s", idx, itf->class_name); + return 0; + } + itf2class_T *i2c; + for (i2c = itf->class_itf2class; i2c != NULL; i2c = i2c->i2c_next) + if (i2c->i2c_class == cl) + break; + if (i2c == NULL) + { + siemsg("class %s not found on interface %s", + cl->class_name, itf->class_name); + return 0; + } + int *table = (int *)(i2c + 1); + return table[idx]; +} + +/* * Handle ":class" and ":abstract class" up to ":endclass". * Handle ":interface" up to ":endinterface". */ @@ -765,22 +791,6 @@ early_ret: cl->class_extends = extends_cl; - if (ga_impl.ga_len > 0) - { - // Move the "implements" names into the class. - cl->class_interface_count = ga_impl.ga_len; - cl->class_interfaces = ALLOC_MULT(char_u *, ga_impl.ga_len); - if (cl->class_interfaces == NULL) - goto cleanup; - for (int i = 0; i < ga_impl.ga_len; ++i) - cl->class_interfaces[i] = ((char_u **)ga_impl.ga_data)[i]; - VIM_CLEAR(ga_impl.ga_data); - ga_impl.ga_len = 0; - - cl->class_interfaces_cl = intf_classes; - intf_classes = NULL; - } - // Add class and object members to "cl". if (add_members_to_class(&classmembers, extends_cl == NULL ? NULL @@ -798,6 +808,48 @@ early_ret: &cl->class_obj_member_count) == FAIL) goto cleanup; + if (ga_impl.ga_len > 0) + { + // Move the "implements" names into the class. + cl->class_interface_count = ga_impl.ga_len; + cl->class_interfaces = ALLOC_MULT(char_u *, ga_impl.ga_len); + if (cl->class_interfaces == NULL) + goto cleanup; + for (int i = 0; i < ga_impl.ga_len; ++i) + cl->class_interfaces[i] = ((char_u **)ga_impl.ga_data)[i]; + VIM_CLEAR(ga_impl.ga_data); + ga_impl.ga_len = 0; + + // For each interface add a lookuptable for the member index on the + // interface to the member index in this class. + for (int i = 0; i < cl->class_interface_count; ++i) + { + class_T *ifcl = intf_classes[i]; + itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T) + + ifcl->class_obj_member_count * sizeof(int)); + if (if2cl == NULL) + goto cleanup; + if2cl->i2c_next = ifcl->class_itf2class; + ifcl->class_itf2class = if2cl; + if2cl->i2c_class = cl; + + for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i) + for (int cl_i = 0; cl_i < cl->class_obj_member_count; ++cl_i) + { + if (STRCMP(ifcl->class_obj_members[if_i].ocm_name, + cl->class_obj_members[cl_i].ocm_name) == 0) + { + int *table = (int *)(if2cl + 1); + table[if_i] = cl_i; + break; + } + } + } + + cl->class_interfaces_cl = intf_classes; + intf_classes = NULL; + } + if (is_class && cl->class_class_member_count > 0) { // Allocate a typval for each class member and initialize it. @@ -1411,6 +1463,13 @@ class_unref(class_T *cl) vim_free(cl->class_interfaces); vim_free(cl->class_interfaces_cl); + itf2class_T *next; + for (itf2class_T *i2c = cl->class_itf2class; i2c != NULL; i2c = next) + { + next = i2c->i2c_next; + vim_free(i2c); + } + for (int i = 0; i < cl->class_class_member_count; ++i) { ocmember_T *m = &cl->class_class_members[i]; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2056,6 +2056,8 @@ compile_load_lhs_with_index(lhs_T *lhs, if (generate_LOAD(cctx, ISN_LOAD, 0, NULL, lhs->lhs_type) == FAIL) return FAIL; + if (cl->class_flags & CLASS_INTERFACE) + return generate_GET_ITF_MEMBER(cctx, cl, lhs->lhs_member_idx, type); return generate_GET_OBJ_MEMBER(cctx, lhs->lhs_member_idx, type); } diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -5184,6 +5184,7 @@ exec_instructions(ectx_T *ectx) break; case ISN_GET_OBJ_MEMBER: + case ISN_GET_ITF_MEMBER: { tv = STACK_TV_BOT(-1); if (tv->v_type != VAR_OBJECT) @@ -5200,8 +5201,20 @@ exec_instructions(ectx_T *ectx) clear_type_list(&type_list); goto on_error; } - int idx = iptr->isn_arg.number; + object_T *obj = tv->vval.v_object; + int idx; + if (iptr->isn_type == ISN_GET_OBJ_MEMBER) + idx = iptr->isn_arg.number; + else + { + idx = iptr->isn_arg.classmember.cm_idx; + // convert the interface index to the object index + idx = object_index_from_itf_index( + iptr->isn_arg.classmember.cm_class, + idx, obj->obj_class); + } + // the members are located right after the object struct typval_T *mtv = ((typval_T *)(obj + 1)) + idx; copy_tv(mtv, tv); @@ -6912,6 +6925,11 @@ list_instructions(char *pfx, isn_T *inst iptr->isn_arg.string); break; case ISN_GET_OBJ_MEMBER: smsg("%s%4d OBJ_MEMBER %d", pfx, current, (int)iptr->isn_arg.number); break; + case ISN_GET_ITF_MEMBER: smsg("%s%4d ITF_MEMBER %d on %s", + pfx, current, + (int)iptr->isn_arg.classmember.cm_idx, + iptr->isn_arg.classmember.cm_class->class_name); + break; case ISN_STORE_THIS: smsg("%s%4d STORE_THIS %d", pfx, current, (int)iptr->isn_arg.number); break; case ISN_CLEARDICT: smsg("%s%4d CLEARDICT", pfx, current); break; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -364,6 +364,8 @@ compile_class_object_index(cctx_T *cctx, } *arg = name_end; + if (cl->class_flags & CLASS_INTERFACE) + return generate_GET_ITF_MEMBER(cctx, cl, i, m->ocm_type); return generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type); } } diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -150,6 +150,26 @@ generate_GET_OBJ_MEMBER(cctx_T *cctx, in } /* + * Generate ISN_GET_ITF_MEMBER - access member of interface at bottom of stack + * by index. + */ + int +generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type) +{ + RETURN_OK_IF_SKIP(cctx); + + // drop the object type + isn_T *isn = generate_instr_drop(cctx, ISN_GET_ITF_MEMBER, 1); + if (isn == NULL) + return FAIL; + + isn->isn_arg.classmember.cm_class = itf; + ++itf->class_refcount; + isn->isn_arg.classmember.cm_idx = idx; + return push_type_stack2(cctx, type, &t_any); +} + +/* * Generate ISN_STORE_THIS - store value in member of "this" object with member * index "idx". */ @@ -2497,6 +2517,7 @@ delete_instr(isn_T *isn) case ISN_LOAD_CLASSMEMBER: case ISN_STORE_CLASSMEMBER: + case ISN_GET_ITF_MEMBER: class_unref(isn->isn_arg.classmember.cm_class); break;