changeset 34508:d7b7fa7edb3b v9.1.0160

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 <yegappan@yahoo.com> 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 <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sat, 09 Mar 2024 16:00:02 +0100
parents ee1ce4ddcb29
children 89bc200aeb24
files src/evalvars.c src/proto/evalvars.pro src/testdir/shared.vim src/testdir/test_vim9_class.vim src/version.c src/vim9class.c
diffstat 6 files changed, 126 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- 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;
 }
 
 /*
--- 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);
--- 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='
--- 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<number> = [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
--- 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,
--- 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);