Patch 9.0.1159
Problem:    Extends argument for class not implemented yet.
Solution:   Basic implementation of "extends".
Files:      src/errors.h, src/vim9class.c, src/structs.h, src/userfunc.c,
            src/proto/userfunc.pro, src/testdir/test_vim9_class.vim


*** ../vim-9.0.1158/src/errors.h        2023-01-07 14:50:59.044362174 +0000
--- src/errors.h        2023-01-08 14:21:50.948883266 +0000
***************
*** 3426,3429 ****
--- 3426,3435 ----
        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
*** ../vim-9.0.1158/src/vim9class.c     2023-01-07 14:50:59.044362174 +0000
--- src/vim9class.c     2023-01-08 19:01:34.290240107 +0000
***************
*** 160,165 ****
--- 160,167 ----
  /*
   * 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,181 ****
      static int
  add_members_to_class(
      garray_T  *gap,
      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)
        return FAIL;
      if (gap->ga_len > 0)
!       mch_memmove(*members, gap->ga_data, sizeof(ocmember_T) * gap->ga_len);
      VIM_CLEAR(gap->ga_data);
      return OK;
  }
--- 169,196 ----
      static int
  add_members_to_class(
      garray_T  *gap,
+     ocmember_T        *parent_members,
+     int               parent_count,
      ocmember_T        **members,
      int               *member_count)
  {
!     *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)
!       // 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,238 ****
--- 248,256 ----
      //    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,249 ****
      while (*arg != NUL && *arg != '#' && *arg != '\n')
      {
        // TODO:
-       //    extends SomeClass
        //    specifies SomeInterface
!       if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10]))
        {
            if (ga_impl.ga_len > 0)
            {
--- 259,287 ----
      while (*arg != NUL && *arg != '#' && *arg != '\n')
      {
        // TODO:
        //    specifies SomeInterface
!       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,294 ****
--- 327,333 ----
        {
            semsg(_(e_trailing_characters_str), arg);
  early_ret:
+           vim_free(extends);
            ga_clear_strings(&ga_impl);
            return;
        }
***************
*** 496,512 ****
      }
      vim_free(theline);
  
!     // Check a few things before defining the class.
      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)
            {
                semsg(_(e_interface_name_not_found_str), impl);
                success = FALSE;
--- 535,584 ----
      }
      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)
!     {
!       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)
      {
        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_IMPORT) == FAIL)
            {
                semsg(_(e_interface_name_not_found_str), impl);
                success = FALSE;
***************
*** 620,625 ****
--- 692,699 ----
        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,645 ****
  
        // Add class and object members to "cl".
        if (add_members_to_class(&classmembers,
!                                   &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)
            goto cleanup;
  
        if (is_class && cl->class_class_member_count > 0)
--- 709,727 ----
  
        // Add class and object members to "cl".
        if (add_members_to_class(&classmembers,
!                                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,
!                                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,754 ****
            ufunc_T ***fup = loop == 1 ? &cl->class_class_functions
                                       : &cl->class_obj_methods;
  
!           *fcount = gap->ga_len;
!           if (gap->ga_len == 0)
            {
                *fup = NULL;
                continue;
            }
!           *fup = ALLOC_MULT(ufunc_T *, gap->ga_len);
            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)
            {
                ufunc_T *fp = (*fup)[i];
                fp->uf_class = cl;
--- 817,863 ----
            ufunc_T ***fup = loop == 1 ? &cl->class_class_functions
                                       : &cl->class_obj_methods;
  
!           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 *, *fcount);
            if (*fup == NULL)
                goto cleanup;
! 
!           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,791 ****
--- 895,902 ----
        vim_free(cl);
      }
  
+     vim_free(extends);
+     class_unref(extends_cl);
      ga_clear_strings(&ga_impl);
  
      for (int round = 1; round <= 2; ++round)
***************
*** 1167,1172 ****
--- 1278,1285 ----
        // 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);
*** ../vim-9.0.1158/src/structs.h       2023-01-06 18:42:16.434674109 +0000
--- src/structs.h       2023-01-08 14:41:24.731883105 +0000
***************
*** 1494,1499 ****
--- 1494,1501 ----
      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
*** ../vim-9.0.1158/src/userfunc.c      2023-01-05 19:59:14.003418087 +0000
--- src/userfunc.c      2023-01-08 19:47:46.187923640 +0000
***************
*** 5516,5521 ****
--- 5516,5589 ----
  }
  
  /*
+  * 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
*** ../vim-9.0.1158/src/proto/userfunc.pro      2023-01-05 19:59:14.003418087 
+0000
--- src/proto/userfunc.pro      2023-01-08 15:45:23.169463349 +0000
***************
*** 56,61 ****
--- 56,62 ----
  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);
*** ../vim-9.0.1158/src/testdir/test_vim9_class.vim     2023-01-07 
14:50:59.044362174 +0000
--- src/testdir/test_vim9_class.vim     2023-01-08 15:18:58.039817720 +0000
***************
*** 753,757 ****
--- 753,823 ----
    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
*** ../vim-9.0.1158/src/version.c       2023-01-08 13:44:21.073352325 +0000
--- src/version.c       2023-01-08 14:11:10.173525123 +0000
***************
*** 697,698 ****
--- 697,700 ----
  {   /* Add new patch number below this line */
+ /**/
+     1159,
  /**/

-- 
"Beware of bugs in the above code; I have only proved
it correct, not tried it." -- Donald Knuth

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20230108195444.18DBC1C044B%40moolenaar.net.

Raspunde prin e-mail lui