diff options
-rw-r--r-- | src/errors.h | 6 | ||||
-rw-r--r-- | src/proto/userfunc.pro | 1 | ||||
-rw-r--r-- | src/structs.h | 2 | ||||
-rw-r--r-- | src/testdir/test_vim9_class.vim | 66 | ||||
-rw-r--r-- | src/userfunc.c | 68 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9class.c | 153 |
7 files changed, 278 insertions, 20 deletions
diff --git a/src/errors.h b/src/errors.h index 5a59f7fbc7..d12eb9ff31 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3426,4 +3426,10 @@ EXTERN char e_duplicate_implements[] 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 diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index a87113faa2..94b53f6766 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -56,6 +56,7 @@ int has_varargs(ufunc_T *ufunc); 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); diff --git a/src/structs.h b/src/structs.h index 78108c18ff..08edcf4020 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1494,6 +1494,8 @@ struct class_S 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 diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim index ef9ef5562d..c7ee2188af 100644 --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -753,5 +753,71 @@ def Test_class_used_as_type() 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 diff --git a/src/userfunc.c b/src/userfunc.c index 032c99cff0..9ac3a44263 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -5516,6 +5516,74 @@ get_user_func_name(expand_T *xp, int idx) } /* + * 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 diff --git a/src/version.c b/src/version.c index 560fb9d00d..a9a54048da 100644 --- 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 */ /**/ + 1159, +/**/ 1158, /**/ 1157, diff --git a/src/vim9class.c b/src/vim9class.c index 4a8dd7c0ef..9b1f913345 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -160,6 +160,8 @@ add_member( /* * 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,15 +169,28 @@ add_member( static int add_members_to_class( garray_T *gap, + ocmember_T *parent_members, + int parent_count, 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) + *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) - mch_memmove(*members, gap->ga_data, sizeof(ocmember_T) * gap->ga_len); + // 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,6 +248,9 @@ ex_class(exarg_T *eap) // generics: <Tkey, Tentry> // 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,9 +259,29 @@ ex_class(exarg_T *eap) while (*arg != NUL && *arg != '#' && *arg != '\n') { // TODO: - // extends SomeClass // specifies SomeInterface - if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10])) + 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,6 +327,7 @@ ex_class(exarg_T *eap) { semsg(_(e_trailing_characters_str), arg); early_ret: + vim_free(extends); ga_clear_strings(&ga_impl); return; } @@ -496,17 +535,50 @@ early_ret: } vim_free(theline); - // Check a few things before defining the class. + 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) { - // 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) + if (eval_variable(impl, 0, 0, &tv, NULL, EVAL_VAR_IMPORT) == FAIL) { semsg(_(e_interface_name_not_found_str), impl); success = FALSE; @@ -620,6 +692,8 @@ early_ret: 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,11 +709,19 @@ early_ret: // Add class and object members to "cl". if (add_members_to_class(&classmembers, - &cl->class_class_members, - &cl->class_class_member_count) == FAIL + 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, - &cl->class_obj_members, - &cl->class_obj_member_count) == FAIL) + 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,20 +817,47 @@ early_ret: ufunc_T ***fup = loop == 1 ? &cl->class_class_functions : &cl->class_obj_methods; - *fcount = gap->ga_len; - if (gap->ga_len == 0) + 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 *, gap->ga_len); + *fup = ALLOC_MULT(ufunc_T *, *fcount); if (*fup == NULL) goto cleanup; - mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len); + + 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 object methods. - for (int i = 0; i < gap->ga_len; ++i) + // 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,6 +895,8 @@ cleanup: vim_free(cl); } + vim_free(extends); + class_unref(extends_cl); ga_clear_strings(&ga_impl); for (int round = 1; round <= 2; ++round) @@ -1167,6 +1278,8 @@ class_unref(class_T *cl) // 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); |