To: vim_dev@googlegroups.com Subject: Patch 9.0.1159 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1159 Problem: Extends argument for class not implemented yet. Solution: Basic implementation of "extends". Files: src/errors.h, src/vim9class.c, src/structs.h, src/userfunc.c, src/proto/userfunc.pro, src/testdir/test_vim9_class.vim *** ../vim-9.0.1158/src/errors.h 2023-01-07 14:50:59.044362174 +0000 --- src/errors.h 2023-01-08 14:21:50.948883266 +0000 *************** *** 3426,3429 **** --- 3426,3435 ---- INIT(= N_("E1350: Duplicate \"implements\"")); EXTERN char e_duplicate_interface_after_implements_str[] INIT(= N_("E1351: Duplicate interface after \"implements\": %s")); + EXTERN char e_duplicate_extends[] + INIT(= N_("E1352: Duplicate \"extends\"")); + EXTERN char e_class_name_not_found_str[] + INIT(= N_("E1353: Class name not found: %s")); + EXTERN char e_cannot_extend_str[] + INIT(= N_("E1354: Cannot extend %s")); #endif *** ../vim-9.0.1158/src/vim9class.c 2023-01-07 14:50:59.044362174 +0000 --- src/vim9class.c 2023-01-08 19:01:34.290240107 +0000 *************** *** 160,165 **** --- 160,167 ---- /* * Move the class or object members found while parsing a class into the class. * "gap" contains the found members. + * "parent_members" points to the members in the parent class (if any) + * "parent_count" is the number of members in the parent class * "members" will be set to the newly allocated array of members and * "member_count" set to the number of members. * Returns OK or FAIL. *************** *** 167,181 **** static int add_members_to_class( garray_T *gap, ocmember_T **members, int *member_count) { ! *member_count = gap->ga_len; ! *members = gap->ga_len == 0 ? NULL : ALLOC_MULT(ocmember_T, gap->ga_len); ! if (gap->ga_len > 0 && *members == NULL) return FAIL; if (gap->ga_len > 0) ! mch_memmove(*members, gap->ga_data, sizeof(ocmember_T) * gap->ga_len); VIM_CLEAR(gap->ga_data); return OK; } --- 169,196 ---- static int add_members_to_class( garray_T *gap, + ocmember_T *parent_members, + int parent_count, ocmember_T **members, int *member_count) { ! *member_count = parent_count + gap->ga_len; ! *members = *member_count == 0 ? NULL ! : ALLOC_MULT(ocmember_T, *member_count); ! if (*member_count > 0 && *members == NULL) return FAIL; + for (int i = 0; i < parent_count; ++i) + { + // parent members need to be copied + *members[i] = parent_members[i]; + members[i]->ocm_name = vim_strsave(members[i]->ocm_name); + if (members[i]->ocm_init != NULL) + members[i]->ocm_init = vim_strsave(members[i]->ocm_init); + } if (gap->ga_len > 0) ! // new members are moved ! mch_memmove(*members + parent_count, ! gap->ga_data, sizeof(ocmember_T) * gap->ga_len); VIM_CLEAR(gap->ga_data); return OK; } *************** *** 233,238 **** --- 248,256 ---- // generics: // handle "is_export" if it is set + // Name for "extends BaseClass" + char_u *extends = NULL; + // Names for "implements SomeInterface" garray_T ga_impl; ga_init2(&ga_impl, sizeof(char_u *), 5); *************** *** 241,249 **** while (*arg != NUL && *arg != '#' && *arg != '\n') { // TODO: - // extends SomeClass // specifies SomeInterface ! if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10])) { if (ga_impl.ga_len > 0) { --- 259,287 ---- while (*arg != NUL && *arg != '#' && *arg != '\n') { // TODO: // specifies SomeInterface ! if (STRNCMP(arg, "extends", 7) == 0 && IS_WHITE_OR_NUL(arg[7])) ! { ! if (extends != NULL) ! { ! emsg(_(e_duplicate_extends)); ! goto early_ret; ! } ! arg = skipwhite(arg + 7); ! char_u *end = find_name_end(arg, NULL, NULL, FNE_CHECK_START); ! if (!IS_WHITE_OR_NUL(*end)) ! { ! semsg(_(e_white_space_required_after_name_str), arg); ! goto early_ret; ! } ! extends = vim_strnsave(arg, end - arg); ! if (extends == NULL) ! goto early_ret; ! ! arg = skipwhite(end + 1); ! } ! else if (STRNCMP(arg, "implements", 10) == 0 ! && IS_WHITE_OR_NUL(arg[10])) { if (ga_impl.ga_len > 0) { *************** *** 289,294 **** --- 327,333 ---- { semsg(_(e_trailing_characters_str), arg); early_ret: + vim_free(extends); ga_clear_strings(&ga_impl); return; } *************** *** 496,512 **** } vim_free(theline); ! // Check a few things before defining the class. if (success && ga_impl.ga_len > 0) { - // Check all "implements" entries are valid and correct. for (int i = 0; i < ga_impl.ga_len && success; ++i) { char_u *impl = ((char_u **)ga_impl.ga_data)[i]; typval_T tv; tv.v_type = VAR_UNKNOWN; ! if (eval_variable(impl, 0, 0, &tv, NULL, ! EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT) == FAIL) { semsg(_(e_interface_name_not_found_str), impl); success = FALSE; --- 535,584 ---- } vim_free(theline); ! class_T *extends_cl = NULL; // class from "extends" argument ! ! /* ! * Check a few things before defining the class. ! */ ! ! // Check the "extends" class is valid. ! if (success && extends != NULL) ! { ! typval_T tv; ! tv.v_type = VAR_UNKNOWN; ! if (eval_variable(extends, 0, 0, &tv, NULL, EVAL_VAR_IMPORT) == FAIL) ! { ! semsg(_(e_class_name_not_found_str), extends); ! success = FALSE; ! } ! else ! { ! if (tv.v_type != VAR_CLASS ! || tv.vval.v_class == NULL ! || (tv.vval.v_class->class_flags & CLASS_INTERFACE) != 0) ! { ! semsg(_(e_cannot_extend_str), extends); ! success = FALSE; ! } ! else ! { ! extends_cl = tv.vval.v_class; ! ++extends_cl->class_refcount; ! } ! clear_tv(&tv); ! } ! } ! VIM_CLEAR(extends); ! ! // Check all "implements" entries are valid. if (success && ga_impl.ga_len > 0) { for (int i = 0; i < ga_impl.ga_len && success; ++i) { char_u *impl = ((char_u **)ga_impl.ga_data)[i]; typval_T tv; tv.v_type = VAR_UNKNOWN; ! if (eval_variable(impl, 0, 0, &tv, NULL, EVAL_VAR_IMPORT) == FAIL) { semsg(_(e_interface_name_not_found_str), impl); success = FALSE; *************** *** 620,625 **** --- 692,699 ---- if (cl->class_name == NULL) goto cleanup; + cl->class_extends = extends_cl; + if (ga_impl.ga_len > 0) { // Move the "implements" names into the class. *************** *** 635,645 **** // Add class and object members to "cl". if (add_members_to_class(&classmembers, ! &cl->class_class_members, ! &cl->class_class_member_count) == FAIL || add_members_to_class(&objmembers, ! &cl->class_obj_members, ! &cl->class_obj_member_count) == FAIL) goto cleanup; if (is_class && cl->class_class_member_count > 0) --- 709,727 ---- // Add class and object members to "cl". if (add_members_to_class(&classmembers, ! extends_cl == NULL ? NULL ! : extends_cl->class_class_members, ! extends_cl == NULL ? 0 ! : extends_cl->class_class_member_count, ! &cl->class_class_members, ! &cl->class_class_member_count) == FAIL || add_members_to_class(&objmembers, ! extends_cl == NULL ? NULL ! : extends_cl->class_obj_members, ! extends_cl == NULL ? 0 ! : extends_cl->class_obj_member_count, ! &cl->class_obj_members, ! &cl->class_obj_member_count) == FAIL) goto cleanup; if (is_class && cl->class_class_member_count > 0) *************** *** 735,754 **** ufunc_T ***fup = loop == 1 ? &cl->class_class_functions : &cl->class_obj_methods; ! *fcount = gap->ga_len; ! if (gap->ga_len == 0) { *fup = NULL; continue; } ! *fup = ALLOC_MULT(ufunc_T *, gap->ga_len); if (*fup == NULL) goto cleanup; ! mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len); vim_free(gap->ga_data); ! // Set the class pointer on all the object methods. ! for (int i = 0; i < gap->ga_len; ++i) { ufunc_T *fp = (*fup)[i]; fp->uf_class = cl; --- 817,863 ---- ufunc_T ***fup = loop == 1 ? &cl->class_class_functions : &cl->class_obj_methods; ! int parent_count = 0; ! if (extends_cl != NULL) ! // Include functions from the parent. ! parent_count = loop == 1 ! ? extends_cl->class_class_function_count ! : extends_cl->class_obj_method_count; ! ! *fcount = parent_count + gap->ga_len; ! if (*fcount == 0) { *fup = NULL; continue; } ! *fup = ALLOC_MULT(ufunc_T *, *fcount); if (*fup == NULL) goto cleanup; ! ! int skipped = 0; ! for (int i = 0; i < parent_count; ++i) ! { ! // Copy functions from the parent. Can't use the same ! // function, because "uf_class" is different and compilation ! // will have a different result. ! // Skip "new" functions. TODO: not all of them. ! if (loop == 1 && STRNCMP( ! extends_cl->class_class_functions[i]->uf_name, ! "new", 3) == 0) ! ++skipped; ! else ! *fup[i - skipped] = copy_function((loop == 1 ! ? extends_cl->class_class_functions ! : extends_cl->class_obj_methods)[i]); ! } ! ! mch_memmove(*fup + parent_count - skipped, gap->ga_data, ! sizeof(ufunc_T *) * gap->ga_len); vim_free(gap->ga_data); + *fcount -= skipped; ! // Set the class pointer on all the functions and object methods. ! for (int i = 0; i < *fcount; ++i) { ufunc_T *fp = (*fup)[i]; fp->uf_class = cl; *************** *** 786,791 **** --- 895,902 ---- vim_free(cl); } + vim_free(extends); + class_unref(extends_cl); ga_clear_strings(&ga_impl); for (int round = 1; round <= 2; ++round) *************** *** 1167,1172 **** --- 1278,1285 ---- // be freed. VIM_CLEAR(cl->class_name); + class_unref(cl->class_extends); + for (int i = 0; i < cl->class_interface_count; ++i) vim_free(((char_u **)cl->class_interfaces)[i]); vim_free(cl->class_interfaces); *** ../vim-9.0.1158/src/structs.h 2023-01-06 18:42:16.434674109 +0000 --- src/structs.h 2023-01-08 14:41:24.731883105 +0000 *************** *** 1494,1499 **** --- 1494,1501 ---- int class_refcount; int class_copyID; // used by garbage collection + class_T *class_extends; // parent class or NULL + // interfaces declared for the class int class_interface_count; char_u **class_interfaces; // allocated array of names *** ../vim-9.0.1158/src/userfunc.c 2023-01-05 19:59:14.003418087 +0000 --- src/userfunc.c 2023-01-08 19:47:46.187923640 +0000 *************** *** 5516,5521 **** --- 5516,5589 ---- } /* + * Make a copy of a function. + * Intended to be used for a function defined on a base class that has a copy + * on the child class. + * The copy has uf_refcount set to one. + * Returns NULL when out of memory. + */ + ufunc_T * + copy_function(ufunc_T *fp) + { + // The struct may have padding, make sure we allocate at least the size of + // the struct. + size_t len = offsetof(ufunc_T, uf_name) + STRLEN(fp->uf_name) + 1; + ufunc_T *ufunc = alloc_clear(len < sizeof(ufunc_T) ? sizeof(ufunc_T) : len); + if (ufunc == NULL) + return NULL; + + // Most things can just be copied. + *ufunc = *fp; + + ufunc->uf_def_status = UF_TO_BE_COMPILED; + ufunc->uf_dfunc_idx = 0; + ufunc->uf_class = NULL; + + ga_copy_strings(&fp->uf_args, &ufunc->uf_args); + ga_copy_strings(&fp->uf_def_args, &ufunc->uf_def_args); + + if (ufunc->uf_arg_types != NULL) + { + // "uf_arg_types" is an allocated array, make a copy. + type_T **at = ALLOC_CLEAR_MULT(type_T *, ufunc->uf_args.ga_len); + if (at != NULL) + { + mch_memmove(at, ufunc->uf_arg_types, + sizeof(type_T *) * ufunc->uf_args.ga_len); + ufunc->uf_arg_types = at; + } + } + + // TODO: how about the types themselves? they can be freed when the + // original function is freed: + // type_T **uf_arg_types; + // type_T *uf_ret_type; + + ufunc->uf_type_list.ga_len = 0; + ufunc->uf_type_list.ga_data = NULL; + + // TODO: partial_T *uf_partial; + + if (ufunc->uf_va_name != NULL) + ufunc->uf_va_name = vim_strsave(ufunc->uf_va_name); + + // TODO: + // type_T *uf_va_type; + // type_T *uf_func_type; + + ufunc->uf_block_depth = 0; + ufunc->uf_block_ids = NULL; + + ga_copy_strings(&fp->uf_lines, &ufunc->uf_lines); + + ufunc->uf_refcount = 1; + ufunc->uf_name_exp = NULL; + STRCPY(ufunc->uf_name, fp->uf_name); + + return ufunc; + } + + /* * ":delfunction {name}" */ void *** ../vim-9.0.1158/src/proto/userfunc.pro 2023-01-05 19:59:14.003418087 +0000 --- src/proto/userfunc.pro 2023-01-08 15:45:23.169463349 +0000 *************** *** 56,61 **** --- 56,62 ---- int function_exists(char_u *name, int no_deref); char_u *get_expanded_name(char_u *name, int check); char_u *get_user_func_name(expand_T *xp, int idx); + ufunc_T *copy_function(ufunc_T *fp); void ex_delfunction(exarg_T *eap); void func_unref(char_u *name); void func_ptr_unref(ufunc_T *fp); *** ../vim-9.0.1158/src/testdir/test_vim9_class.vim 2023-01-07 14:50:59.044362174 +0000 --- src/testdir/test_vim9_class.vim 2023-01-08 15:18:58.039817720 +0000 *************** *** 753,757 **** --- 753,823 ---- v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object but got string') enddef + def Test_class_extends() + var lines =<< trim END + vim9script + class Base + this.one = 1 + def GetOne(): number + return this.one + enddef + endclass + class Child extends Base + this.two = 2 + def GetTotal(): number + return this.one + this.two + enddef + endclass + var o = Child.new() + assert_equal(1, o.one) + assert_equal(2, o.two) + assert_equal(1, o.GetOne()) + assert_equal(3, o.GetTotal()) + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + class Base + this.one = 1 + endclass + class Child extends Base + this.two = 2 + endclass + var o = Child.new(3, 44) + assert_equal(3, o.one) + assert_equal(44, o.two) + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + class Base + this.one = 1 + endclass + class Child extends Base extends Base + this.two = 2 + endclass + END + v9.CheckScriptFailure(lines, 'E1352: Duplicate "extends"') + + lines =<< trim END + vim9script + class Child extends BaseClass + this.two = 2 + endclass + END + v9.CheckScriptFailure(lines, 'E1353: Class name not found: BaseClass') + + lines =<< trim END + vim9script + var SomeVar = 99 + class Child extends SomeVar + this.two = 2 + endclass + END + v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar') + enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-9.0.1158/src/version.c 2023-01-08 13:44:21.073352325 +0000 --- src/version.c 2023-01-08 14:11:10.173525123 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1159, /**/ -- "Beware of bugs in the above code; I have only proved it correct, not tried it." -- Donald Knuth /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///