]> granicus.if.org Git - vim/commitdiff
patch 9.0.1159: extends argument for class not implemented yet v9.0.1159
authorBram Moolenaar <Bram@vim.org>
Sun, 8 Jan 2023 19:54:10 +0000 (19:54 +0000)
committerBram Moolenaar <Bram@vim.org>
Sun, 8 Jan 2023 19:54:10 +0000 (19:54 +0000)
Problem:    Extends argument for class not implemented yet.
Solution:   Basic implementation of "extends".

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

index 5a59f7fbc71800371c82f0c5543e3e4a0dd04e68..d12eb9ff319ce81cbfbeb6f2be80489321b9aaeb 100644 (file)
@@ -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
index a87113faa248f61ebb96be23c567e043a97de43c..94b53f6766df60302129eb7112c933b665dc80ea 100644 (file)
@@ -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);
index 78108c18ffe6e38bea8d1f58dc57c3cd74fd1889..08edcf4020a796850b99ea76e46c5744e7c30cca 100644 (file)
@@ -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
index ef9ef5562d38d732bbe86b8e4ca5d9b88f5f54c5..c7ee2188aff9b8c00115349489d319856ed4d454 100644 (file)
@@ -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
index 032c99cff02e658b86cf4933ddc26da74ad67491..9ac3a442631b4b0936e4686b500b60b4b85d42f2 100644 (file)
@@ -5515,6 +5515,74 @@ get_user_func_name(expand_T *xp, int idx)
     return NULL;
 }
 
+/*
+ * 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}"
  */
index 560fb9d00df19aed0c0072c1193a27b42fdd024c..a9a54048dafabb89b9fcbffa110f45b2e7a31425 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1159,
 /**/
     1158,
 /**/
index 4a8dd7c0ef654012ea684fe656f920e074eb160d..9b1f91334587d462b9482db7b132a3bebf48b151 100644 (file)
@@ -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);