]> granicus.if.org Git - vim/commitdiff
patch 9.0.1178: a child class cannot override functions from a base class v9.0.1178
authorBram Moolenaar <Bram@vim.org>
Wed, 11 Jan 2023 15:59:05 +0000 (15:59 +0000)
committerBram Moolenaar <Bram@vim.org>
Wed, 11 Jan 2023 15:59:05 +0000 (15:59 +0000)
Problem:    A child class cannot override functions from a base class.
Solution:   Allow overriding and implement "super".

src/errors.h
src/globals.h
src/structs.h
src/testdir/test_vim9_class.vim
src/version.c
src/vim9class.c
src/vim9compile.c
src/vim9expr.c

index d12eb9ff319ce81cbfbeb6f2be80489321b9aaeb..6fa1abb787f94057c72b368b4cd0af461d031346 100644 (file)
@@ -3432,4 +3432,10 @@ 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"));
+EXTERN char e_duplicate_function_str[]
+       INIT(= N_("E1355: Duplicate function: %s"));
+EXTERN char e_super_must_be_followed_by_dot[]
+       INIT(= N_("E1356: \"super\" must be followed by a dot"));
+EXTERN char e_using_super_not_in_class_function[]
+       INIT(= N_("E1357: Using \"super\" not in a class function"));
 #endif
index fd5a0cb020eecf187c4424b8fca1ec99451f0904..8c3cb62e4379a915076e7e270bf31e7187049948 100644 (file)
@@ -527,7 +527,10 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);
 #define t_dict_string          (static_types[76])
 #define t_const_dict_string    (static_types[77])
 
-EXTERN type_T static_types[78]
+#define t_super                        (static_types[78])
+#define t_const_super          (static_types[79])
+
+EXTERN type_T static_types[80]
 #ifdef DO_INIT
 = {
     // 0: t_unknown
@@ -685,6 +688,10 @@ EXTERN type_T static_types[78]
     // 76: t_dict_string
     {VAR_DICT, 0, 0, TTFLAG_STATIC, &t_string, NULL},
     {VAR_DICT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &t_string, NULL},
+
+    // 78: t_super (VAR_CLASS with tt_member set to &t_bool
+    {VAR_CLASS, 0, 0, TTFLAG_STATIC, &t_bool, NULL},
+    {VAR_CLASS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &t_bool, NULL},
 }
 #endif
 ;
index 08edcf4020a796850b99ea76e46c5744e7c30cca..89ed23dbdbb6749994e185df43c02378eebc0736 100644 (file)
@@ -1465,6 +1465,7 @@ typedef struct {
 #define TTFLAG_NUMBER_OK    0x04    // tt_type is VAR_FLOAT, VAR_NUMBER is OK
 #define TTFLAG_STATIC      0x08    // one of the static types, e.g. t_any
 #define TTFLAG_CONST       0x10    // cannot be changed
+#define TTFLAG_SUPER       0x20    // object from "super".
 
 typedef enum {
     ACCESS_PRIVATE,    // read/write only inside th class
@@ -1506,7 +1507,8 @@ struct class_S
     typval_T   *class_members_tv;      // allocated array of class member vals
 
     // class functions: "static def SomeMethod()"
-    int                class_class_function_count;
+    int                class_class_function_count;         // total count
+    int                class_class_function_count_child;   // count without "extends"
     ufunc_T    **class_class_functions;        // allocated
 
     // object members: "this.varname"
@@ -1514,7 +1516,8 @@ struct class_S
     ocmember_T *class_obj_members;     // allocated
 
     // object methods: "def SomeMethod()"
-    int                class_obj_method_count;
+    int                class_obj_method_count;             // total count
+    int                class_obj_method_count_child;       // count without "extends"
     ufunc_T    **class_obj_methods;    // allocated
 
     garray_T   class_type_list;        // used for type pointers
index c7ee2188aff9b8c00115349489d319856ed4d454..220cb7578eff7d8a38066ed91460d46f75d6b8f3 100644 (file)
@@ -817,6 +817,27 @@ def Test_class_extends()
       endclass
   END
   v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar')
+
+  lines =<< trim END
+      vim9script
+      class Base
+        this.name: string
+        def ToString(): string
+          return this.name
+        enddef
+      endclass
+
+      class Child extends Base
+        this.age: number
+        def ToString(): string
+          return super.ToString() .. ': ' .. this.age
+        enddef
+      endclass
+
+      var o = Child.new('John', 42)
+      assert_equal('John: 42', o.ToString())
+  END
+  v9.CheckScriptSuccess(lines)
 enddef
 
 
index 2565fcdca8e804b9b883920e55296c32526d0640..1c8bf60861ad410c5b87d0ec632ade41e3c76daf 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1178,
 /**/
     1177,
 /**/
index 9b1f91334587d462b9482db7b132a3bebf48b151..0970619b91f5d6362e3be6c283d03e9a6d99c8c1 100644 (file)
@@ -487,9 +487,21 @@ early_ret:
 
            if (uf != NULL)
            {
-               int is_new = STRNCMP(uf->uf_name, "new", 3) == 0;
+               char_u *name = uf->uf_name;
+               int is_new = STRNCMP(name, "new", 3) == 0;
                garray_T *fgap = has_static || is_new
                                               ? &classfunctions : &objmethods;
+               // Check the name isn't used already.
+               for (int i = 0; i < fgap->ga_len; ++i)
+               {
+                   char_u *n = ((ufunc_T **)fgap->ga_data)[i]->uf_name;
+                   if (STRCMP(name, n) == 0)
+                   {
+                       semsg(_(e_duplicate_function_str), name);
+                       break;
+                   }
+               }
+
                if (ga_grow(fgap, 1) == OK)
                {
                    if (is_new)
@@ -793,7 +805,8 @@ early_ret:
 
            if (nf != NULL && ga_grow(&classfunctions, 1) == OK)
            {
-               ((ufunc_T **)classfunctions.ga_data)[classfunctions.ga_len] = nf;
+               ((ufunc_T **)classfunctions.ga_data)[classfunctions.ga_len]
+                                                                         = nf;
                ++classfunctions.ga_len;
 
                nf->uf_flags |= FC_NEW;
@@ -808,6 +821,7 @@ early_ret:
            }
        }
 
+       // Move all the functions into the created class.
        // loop 1: class functions, loop 2: object methods
        for (int loop = 1; loop <= 2; ++loop)
        {
@@ -834,26 +848,52 @@ early_ret:
            if (*fup == NULL)
                goto cleanup;
 
+           mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len);
+           vim_free(gap->ga_data);
+           if (loop == 1)
+               cl->class_class_function_count_child = gap->ga_len;
+           else
+               cl->class_obj_method_count_child = 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.
+               // Put them after the functions in the current class, object
+               // methods may be overruled, then "super.Method()" is used to
+               // find a method from the parent.
                // 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
+               {
+                   ufunc_T *pf = (loop == 1
                                        ? extends_cl->class_class_functions
-                                       : extends_cl->class_obj_methods)[i]);
+                                       : extends_cl->class_obj_methods)[i];
+                   (*fup)[gap->ga_len + i - skipped] = copy_function(pf);
+
+                   // If the child class overrides a function from the parent
+                   // the signature must be equal.
+                   char_u *pname = pf->uf_name;
+                   for (int ci = 0; ci < gap->ga_len; ++ci)
+                   {
+                       ufunc_T *cf = (*fup)[ci];
+                       char_u *cname = cf->uf_name;
+                       if (STRCMP(pname, cname) == 0)
+                       {
+                           where_T where = WHERE_INIT;
+                           where.wt_func_name = (char *)pname;
+                           (void)check_type(pf->uf_func_type, cf->uf_func_type,
+                                                                 TRUE, where);
+                       }
+                   }
+               }
            }
 
-           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.
index d8e4ae6ed547611b48955db68b895cb020cc2367..9fe850775c8ddb284d42f71796ce065d3bc87806 100644 (file)
@@ -43,16 +43,31 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx)
     if (len == 0)
        return FAIL;
 
-    if (len == 4 && STRNCMP(name, "this", 4) == 0
+    if (((len == 4 && STRNCMP(name, "this", 4) == 0)
+               || (len == 5 && STRNCMP(name, "super", 5) == 0))
            && cctx->ctx_ufunc != NULL
            && (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW)))
     {
+       int is_super = *name == 's';
        if (lvar != NULL)
        {
            CLEAR_POINTER(lvar);
-           lvar->lv_name = (char_u *)"this";
+           lvar->lv_name = (char_u *)(is_super ? "super" : "this");
            if (cctx->ctx_ufunc->uf_class != NULL)
+           {
                lvar->lv_type = &cctx->ctx_ufunc->uf_class->class_object_type;
+               if (is_super)
+               {
+                   type_T *type = get_type_ptr(cctx->ctx_type_list);
+
+                   if (type != NULL)
+                   {
+                       *type = *lvar->lv_type;
+                       lvar->lv_type = type;
+                       type->tt_flags |= TTFLAG_SUPER;
+                   }
+               }
+           }
        }
        return OK;
     }
index 7f5f4c8b0d94cfe29e070fe4ede425591c8cd7ef..27f3bc465f4c626bd02fb9b7bd2adb20316583c3 100644 (file)
@@ -263,7 +263,21 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
        return FAIL;
     }
 
-    if (type->tt_type == VAR_CLASS)
+    class_T *cl = (class_T *)type->tt_member;
+    int is_super = type->tt_flags & TTFLAG_SUPER;
+    if (type == &t_super)
+    {
+       if (cctx->ctx_ufunc == NULL || cctx->ctx_ufunc->uf_class == NULL)
+           emsg(_(e_using_super_not_in_class_function));
+       else
+       {
+           is_super = TRUE;
+           cl = cctx->ctx_ufunc->uf_class;
+           // Remove &t_super from the stack.
+           --cctx->ctx_type_stack.ga_len;
+       }
+    }
+    else if (type->tt_type == VAR_CLASS)
     {
        garray_T *instr = &cctx->ctx_instr;
        if (instr->ga_len > 0)
@@ -286,26 +300,28 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
        return FAIL;
     size_t len = name_end - name;
 
-    class_T *cl = (class_T *)type->tt_member;
     if (*name_end == '(')
     {
        int     function_count;
+       int     child_count;
        ufunc_T **functions;
 
        if (type->tt_type == VAR_CLASS)
        {
            function_count = cl->class_class_function_count;
+           child_count = cl->class_class_function_count_child;
            functions = cl->class_class_functions;
        }
        else
        {
            // type->tt_type == VAR_OBJECT: method call
            function_count = cl->class_obj_method_count;
+           child_count = cl->class_obj_method_count_child;
            functions = cl->class_obj_methods;
        }
 
        ufunc_T *ufunc = NULL;
-       for (int i = 0; i < function_count; ++i)
+       for (int i = is_super ? child_count : 0; i < function_count; ++i)
        {
            ufunc_T *fp = functions[i];
            // Use a separate pointer to avoid that ASAN complains about
@@ -643,7 +659,17 @@ compile_load(
        if (name == NULL)
            return FAIL;
 
-       if (vim_strchr(name, AUTOLOAD_CHAR) != NULL)
+       if (STRCMP(name, "super") == 0
+               && cctx->ctx_ufunc != NULL
+               && (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW)) == 0)
+       {
+           // super.SomeFunc() in a class function: push &t_super type, this
+           // is recognized in compile_subscript().
+           res = push_type_stack(cctx, &t_super);
+           if (*end != '.')
+               emsg(_(e_super_must_be_followed_by_dot));
+       }
+       else if (vim_strchr(name, AUTOLOAD_CHAR) != NULL)
        {
            script_autoload(name, FALSE);
            res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any);