# HG changeset patch # User Christian Brabandt # Date 1709996402 -3600 # Node ID d7b7fa7edb3b1d837f4b51fec3698873a8dc5219 # Parent ee1ce4ddcb295a393af9f8444e0e3106fc17899f patch 9.1.0160: Vim9: Add support for using a class type of itself in an object method Commit: https://github.com/vim/vim/commit/35b867b685cedbcbba9d44695077ecc9a6995f4c Author: Yegappan Lakshmanan Date: Sat Mar 9 15:44:19 2024 +0100 patch 9.1.0160: Vim9: Add support for using a class type of itself in an object method Problem: Add support for using a class type of itself in an object method (thinca) Solution: Vim9: Add support for using a class type of itself in an object method (Yegappan Lakshmanan) fixes: #12369 closes: #14165 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -3830,7 +3830,7 @@ set_var( * If the variable already exists and "is_const" is FALSE the value is updated. * Otherwise the variable is created. */ - void + int set_var_const( char_u *name, scid_T sid, @@ -3854,6 +3854,7 @@ set_var_const( int var_in_autoload = FALSE; int flags = flags_arg; int free_tv_arg = !copy; // free tv_arg if not used + int rc = FAIL; if (sid != 0) { @@ -4127,10 +4128,14 @@ set_var_const( // values. item_lock(dest_tv, DICT_MAXNEST, TRUE, TRUE); + rc = OK; + failed: vim_free(name_tofree); if (free_tv_arg) clear_tv(tv_arg); + + return rc; } /* diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -77,7 +77,7 @@ void vars_clear_ext(hashtab_T *ht, int f void delete_var(hashtab_T *ht, hashitem_T *hi); int before_set_vvar(char_u *varname, dictitem_T *di, typval_T *tv, int copy, int *type_error); void set_var(char_u *name, typval_T *tv, int copy); -void set_var_const(char_u *name, scid_T sid, type_T *type_arg, typval_T *tv_arg, int copy, int flags_arg, int var_idx); +int set_var_const(char_u *name, scid_T sid, type_T *type_arg, typval_T *tv_arg, int copy, int flags_arg, int var_idx); int var_check_permission(dictitem_T *di, char_u *name); int var_check_ro(int flags, char_u *name, int use_gettext); int var_check_lock(int flags, char_u *name, int use_gettext); diff --git a/src/testdir/shared.vim b/src/testdir/shared.vim --- a/src/testdir/shared.vim +++ b/src/testdir/shared.vim @@ -301,7 +301,7 @@ func GetVimCommand(...) let cmd .= ' --not-a-term' let cmd .= ' --gui-dialog-file guidialogfile' " remove any environment variables - let cmd = substitute(cmd, '[A-Z_]*=\S\+ *', '', 'g') + let cmd = substitute(cmd, '[A-Z_]\+=\S\+ *', '', 'g') " If using valgrind, make sure every run uses a different log file. if cmd =~ 'valgrind.*--log-file=' 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 @@ -67,6 +67,22 @@ def Test_class_basic() END v9.CheckSourceFailure(lines, "E488: Trailing characters: | echo 'done'", 3) + # Try to define a class with the same name as an existing variable + lines =<< trim END + vim9script + var Something: list = [1] + class Thing + endclass + interface Api + endinterface + class Something extends Thing implements Api + var v1: string = '' + def Foo() + enddef + endclass + END + v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "Something"', 7) + # Use old "this." prefixed member variable declaration syntax (without initialization) lines =<< trim END vim9script @@ -10272,4 +10288,65 @@ func Test_object_string() call v9.CheckSourceSuccess(lines) endfunc +" Test for using a class in the class definition +def Test_Ref_Class_Within_Same_Class() + var lines =<< trim END + vim9script + class A + var n: number = 0 + def Equals(other: A): bool + return this.n == other.n + enddef + endclass + + var a1 = A.new(10) + var a2 = A.new(10) + var a3 = A.new(20) + assert_equal(true, a1.Equals(a2)) + assert_equal(false, a2.Equals(a3)) + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + + class Foo + var num: number + def Clone(): Foo + return Foo.new(this.num) + enddef + endclass + + var f1 = Foo.new(1) + + def F() + var f2: Foo = f1.Clone() + assert_equal(false, f2 is f1) + assert_equal(true, f2.num == f1.num) + enddef + F() + + var f3: Foo = f1.Clone() + assert_equal(false, f3 is f1) + assert_equal(true, f3.num == f1.num) + END + v9.CheckScriptSuccess(lines) + + # Test for trying to use a class to extend when defining the same class + lines =<< trim END + vim9script + class A extends A + endclass + END + v9.CheckScriptFailure(lines, 'E1354: Cannot extend A', 3) + + # Test for trying to use a class to implement when defining the same class + lines =<< trim END + vim9script + class A implements A + endclass + END + v9.CheckScriptFailure(lines, 'E1347: Not a valid interface: A', 3) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 160, +/**/ 159, /**/ 158, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -208,7 +208,7 @@ add_member( * "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. + * Returns OK on success and FAIL on memory allocation failure. */ static int add_members_to_class( @@ -301,6 +301,7 @@ object_index_from_itf_index(class_T *itf */ static int validate_extends_class( + class_T *cl, char_u *extends_name, class_T **extends_clp, int is_class) @@ -308,6 +309,12 @@ validate_extends_class( typval_T tv; int success = FALSE; + if (STRCMP(cl->class_name, extends_name) == 0) + { + semsg(_(e_cannot_extend_str), extends_name); + return success; + } + tv.v_type = VAR_UNKNOWN; if (eval_variable_import(extends_name, &tv) == FAIL) { @@ -1642,6 +1649,36 @@ early_ret: garray_T objmethods; ga_init2(&objmethods, sizeof(ufunc_T *), 10); + class_T *cl = NULL; + class_T *extends_cl = NULL; // class from "extends" argument + class_T **intf_classes = NULL; + + cl = ALLOC_CLEAR_ONE(class_T); + if (cl == NULL) + goto cleanup; + if (!is_class) + cl->class_flags = CLASS_INTERFACE; + else if (is_abstract) + cl->class_flags = CLASS_ABSTRACT; + + cl->class_refcount = 1; + cl->class_name = vim_strnsave(name_start, name_end - name_start); + if (cl->class_name == NULL) + goto cleanup; + + // Add the class to the script-local variables. + // TODO: handle other context, e.g. in a function + // TODO: does uf_hash need to be cleared? + typval_T tv; + tv.v_type = VAR_CLASS; + tv.vval.v_class = cl; + is_export = class_export; + SOURCING_LNUM = start_lnum; + int rc = set_var_const(cl->class_name, current_sctx.sc_sid, + NULL, &tv, FALSE, 0, 0); + if (rc == FAIL) + goto cleanup; + /* * Go over the body of the class/interface until "endclass" or * "endinterface" is found. @@ -1981,15 +2018,13 @@ early_ret: } 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) - success = validate_extends_class(extends, &extends_cl, is_class); + success = validate_extends_class(cl, extends, &extends_cl, is_class); VIM_CLEAR(extends); // Check the new object methods to make sure their access (public or @@ -2016,8 +2051,6 @@ early_ret: success = validate_abstract_class_methods(&classfunctions, &objmethods, extends_cl); - class_T **intf_classes = NULL; - // Check all "implements" entries are valid. if (success && ga_impl.ga_len > 0) { @@ -2032,24 +2065,10 @@ early_ret: success = check_func_arg_names(&classfunctions, &objmethods, &classmembers); - class_T *cl = NULL; if (success) { // "endclass" encountered without failures: Create the class. - cl = ALLOC_CLEAR_ONE(class_T); - if (cl == NULL) - goto cleanup; - if (!is_class) - cl->class_flags = CLASS_INTERFACE; - else if (is_abstract) - cl->class_flags = CLASS_ABSTRACT; - - cl->class_refcount = 1; - cl->class_name = vim_strnsave(name_start, name_end - name_start); - if (cl->class_name == NULL) - goto cleanup; - if (extends_cl != NULL) { cl->class_extends = extends_cl; @@ -2136,41 +2155,10 @@ early_ret: // TODO: // - Fill hashtab with object members and methods ? - // Add the class to the script-local variables. - // TODO: handle other context, e.g. in a function - // TODO: does uf_hash need to be cleared? - typval_T tv; - tv.v_type = VAR_CLASS; - tv.vval.v_class = cl; - is_export = class_export; - SOURCING_LNUM = start_lnum; - set_var_const(cl->class_name, current_sctx.sc_sid, - NULL, &tv, FALSE, 0, 0); return; } cleanup: - if (cl != NULL) - { - vim_free(cl->class_name); - vim_free(cl->class_class_functions); - if (cl->class_interfaces != NULL) - { - for (int i = 0; i < cl->class_interface_count; ++i) - vim_free(cl->class_interfaces[i]); - vim_free(cl->class_interfaces); - } - if (cl->class_interfaces_cl != NULL) - { - for (int i = 0; i < cl->class_interface_count; ++i) - class_unref(cl->class_interfaces_cl[i]); - vim_free(cl->class_interfaces_cl); - } - vim_free(cl->class_obj_members); - vim_free(cl->class_obj_methods); - vim_free(cl); - } - vim_free(extends); class_unref(extends_cl);