changeset 31653:ec76f9d2319e v9.0.1159

patch 9.0.1159: extends argument for class not implemented yet Commit: https://github.com/vim/vim/commit/8367716a6e9589d61a771e6c329da05c9b55e61a Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jan 8 19:54:10 2023 +0000 patch 9.0.1159: extends argument for class not implemented yet Problem: Extends argument for class not implemented yet. Solution: Basic implementation of "extends".
author Bram Moolenaar <Bram@vim.org>
date Sun, 08 Jan 2023 21:00:05 +0100
parents db9e6699d7b3
children dc77b797c102
files src/errors.h src/proto/userfunc.pro src/structs.h src/testdir/test_vim9_class.vim src/userfunc.c src/version.c src/vim9class.c
diffstat 7 files changed, 280 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- 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"));
-#endif
+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
--- 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);
--- 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
--- 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
--- 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
--- 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,
--- 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);
-	    vim_free(gap->ga_data);
 
-	    // Set the class pointer on all the object methods.
-	    for (int i = 0; i < gap->ga_len; ++i)
+	    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,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);