]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1885: Vim9: no support for abstract methods v9.0.1885
authorYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 8 Sep 2023 17:27:51 +0000 (19:27 +0200)
committerChristian Brabandt <cb@256bit.org>
Fri, 8 Sep 2023 17:29:31 +0000 (19:29 +0200)
Problem:  Vim9: no support for abstract methods
Solution: Add support for defining abstract methods in an abstract class

closes: #13044
closes: #13046

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
runtime/doc/tags
runtime/doc/vim9class.txt
src/errors.h
src/structs.h
src/testdir/test_vim9_class.vim
src/userfunc.c
src/version.c
src/vim.h
src/vim9class.c

index ae893292a8396db2463956512f8ec05f3a294cb6..6c32c96cb84c7ea9c0f24ed9ad3ea2927e6a0e8f 100644 (file)
@@ -5850,6 +5850,7 @@ abandon   editing.txt     /*abandon*
 abbreviations  map.txt /*abbreviations*
 abel.vim       syntax.txt      /*abel.vim*
 abs()  builtin.txt     /*abs()*
+abstract-method        vim9class.txt   /*abstract-method*
 acos() builtin.txt     /*acos()*
 active-buffer  windows.txt     /*active-buffer*
 ada#Create_Tags()      ft_ada.txt      /*ada#Create_Tags()*
index 8a9e37e81230c9544628fd5b0182cfdb90439f72..20ad4bbab59879b214df77dd62d75b219165a3ae 100644 (file)
@@ -358,6 +358,16 @@ class, for which objects can be created.  Example: >
 An abstract class is defined the same way as a normal class, except that it
 does not have any new() method. *E1359*
 
+                                               *abstract-method*
+An abstract method can be defined in an abstract class by using the "abstract"
+prefix when defining the function: >
+
+       abstract class Shape
+          abstract def Draw()
+       endclass
+
+A class extending the abstract class must implement all the abstract methods.
+Class methods in an abstract class can also be abstract methods.
 
 ==============================================================================
 
index fc40fe865fa6b98811ac118ddd0693a1c7824d79..57769062b1c4d30fcffd24b03519790d28f4b7ed 100644 (file)
@@ -3495,6 +3495,12 @@ EXTERN char e_duplicate_member_str[]
        INIT(= N_("E1369: Duplicate member: %s"));
 EXTERN char e_cannot_define_new_function_as_static[]
        INIT(= N_("E1370: Cannot define a \"new\" function as static"));
+EXTERN char e_abstract_must_be_followed_by_def_or_static[]
+       INIT(= N_("E1371: Abstract must be followed by \"def\" or \"static\""));
+EXTERN char e_abstract_method_in_concrete_class[]
+       INIT(= N_("E1372: Abstract method \"%s\" cannot be defined in a concrete class"));
+EXTERN char e_abstract_method_str_not_found[]
+       INIT(= N_("E1373: Abstract method \"%s\" is not implemented"));
 EXTERN char e_cannot_mix_positional_and_non_positional_str[]
        INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s"));
 EXTERN char e_fmt_arg_nr_unused_str[]
index 7acd8574502d6ef0629f563440ea7bcd89b4484b..9214096973ccd66a6654f5a6c8d1ba43a3161611 100644 (file)
@@ -1515,6 +1515,7 @@ struct itf2class_S {
 
 #define CLASS_INTERFACE            1
 #define CLASS_EXTENDED     2       // another class extends this one
+#define CLASS_ABSTRACT     4       // abstract class
 
 // "class_T": used for v_class of typval of VAR_CLASS
 // Also used for an interface (class_flags has CLASS_INTERFACE).
@@ -1875,6 +1876,7 @@ struct ufunc_S
 
 #define FC_OBJECT   0x4000     // object method
 #define FC_NEW     0x8000      // constructor
+#define FC_ABSTRACT 0x10000    // abstract method
 
 #define MAX_FUNC_ARGS  20      // maximum number of function arguments
 #define VAR_SHORT_LEN  20      // short variable name length
index a8c95a74da1ad604ceda5f283de3e3861474be90..9f1e91d86c8c4b4f7f030d1483d38827f7bc366e 100644 (file)
@@ -4473,4 +4473,140 @@ enddef
 "   v9.CheckScriptSuccess(lines)
 " enddef
 
+" Test for abstract methods
+def Test_abstract_method()
+  # Use two abstract methods
+  var lines =<< trim END
+    vim9script
+    abstract class A
+      def M1(): number
+        return 10
+      enddef
+      abstract def M2(): number
+      abstract def M3(): number
+    endclass
+    class B extends A
+      def M2(): number
+        return 20
+      enddef
+      def M3(): number
+        return 30
+      enddef
+    endclass
+    var b = B.new()
+    assert_equal([10, 20, 30], [b.M1(), b.M2(), b.M3()])
+  END
+  v9.CheckScriptSuccess(lines)
+
+  # Don't define an abstract method
+  lines =<< trim END
+    vim9script
+    abstract class A
+      abstract def Foo()
+    endclass
+    class B extends A
+    endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1373: Abstract method "Foo" is not implemented')
+
+  # Use abstract method in a concrete class
+  lines =<< trim END
+    vim9script
+    class A
+      abstract def Foo()
+    endclass
+    class B extends A
+    endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1372: Abstract method "abstract def Foo()" cannot be defined in a concrete class')
+
+  # Use abstract method in an interface
+  lines =<< trim END
+    vim9script
+    interface A
+      abstract def Foo()
+    endinterface
+    class B implements A
+    endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1372: Abstract method "abstract def Foo()" cannot be defined in a concrete class')
+
+  # Abbreviate the "abstract" keyword
+  lines =<< trim END
+    vim9script
+    class A
+      abs def Foo()
+    endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1065: Command cannot be shortened: abs def Foo()')
+
+  # Use "abstract" with a member variable
+  lines =<< trim END
+    vim9script
+    abstract class A
+      abstract this.val = 10
+    endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1371: Abstract must be followed by "def" or "static"')
+
+  # Use a static abstract method
+  lines =<< trim END
+    vim9script
+    abstract class A
+      abstract static def Foo(): number
+    endclass
+    class B extends A
+      static def Foo(): number
+        return 4
+      enddef
+    endclass
+    assert_equal(4, B.Foo())
+  END
+  v9.CheckScriptSuccess(lines)
+
+  # Type mismatch between abstract method and concrete method
+  lines =<< trim END
+    vim9script
+    abstract class A
+      abstract def Foo(a: string, b: number): list<number>
+    endclass
+    class B extends A
+      def Foo(a: number, b: string): list<string>
+        return []
+      enddef
+    endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1407: Member "Foo": type mismatch, expected func(string, number): list<number> but got func(number, string): list<string>')
+
+  # Use an abstract class to invoke an abstract method
+  # FIXME: This should fail
+  lines =<< trim END
+    vim9script
+    abstract class A
+      abstract static def Foo()
+    endclass
+    A.Foo()
+  END
+  v9.CheckScriptSuccess(lines)
+
+  # Invoke an abstract method from a def function
+  lines =<< trim END
+    vim9script
+    abstract class A
+      abstract def Foo(): list<number>
+    endclass
+    class B extends A
+      def Foo(): list<number>
+        return [3, 5]
+      enddef
+    endclass
+    def Bar(c: B)
+      assert_equal([3, 5], c.Foo())
+    enddef
+    var b = B.new()
+    Bar(b)
+  END
+  v9.CheckScriptSuccess(lines)
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index 6638849dbb43650e556e7a40000d6534e773b4bb..efde29dcb169799de0b5565c28a727c0c19d63df 100644 (file)
@@ -5021,6 +5021,7 @@ define_function(
     // Do not define the function when getting the body fails and when
     // skipping.
     if (((class_flags & CF_INTERFACE) == 0
+               && (class_flags & CF_ABSTRACT_METHOD) == 0
                && get_function_body(eap, &newlines, line_arg, lines_to_free)
                                                                       == FAIL)
            || eap->skip)
index 9797b234e72be5d9ed90b130d5405ab1019aad55..8faa534b21b5e5a85efb4acd6e4c6d1d598f4f31 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1885,
 /**/
     1884,
 /**/
index 265fd738e2899b137a61a9c249fd32073a95ff7d..6b05a2ae6783bdd4528d39631274f08c67103b18 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -2915,5 +2915,6 @@ long elapsed(DWORD start_tick);
 // Flags used by "class_flags" of define_function()
 #define CF_CLASS       1       // inside a class
 #define CF_INTERFACE   2       // inside an interface
+#define CF_ABSTRACT_METHOD     4       // inside an abstract class
 
 #endif // VIM__H
index 9328ee713c0efe0793e9f2f457461a70e1af2390..8e2c88b14df699e93107761cf14c60610b009609 100644 (file)
@@ -308,21 +308,19 @@ validate_extends_class(char_u *extends_name, class_T **extends_clp)
        semsg(_(e_class_name_not_found_str), extends_name);
        return success;
     }
+
+    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_name);
     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_name);
-       else
-       {
-           class_T *extends_cl = tv.vval.v_class;
-           ++extends_cl->class_refcount;
-           *extends_clp = extends_cl;
-           success = TRUE;
-       }
-       clear_tv(&tv);
+       class_T *extends_cl = tv.vval.v_class;
+       ++extends_cl->class_refcount;
+       *extends_clp = extends_cl;
+       success = TRUE;
     }
+    clear_tv(&tv);
 
     return success;
 }
@@ -391,6 +389,65 @@ validate_extends_members(
     return TRUE;
 }
 
+/*
+ * When extending an abstract class, check whether all the abstract methods in
+ * the parent class are implemented.  Returns TRUE if all the methods are
+ * implemented.
+ */
+    static int
+validate_extends_methods(
+    garray_T   *classmethods_gap,
+    garray_T   *objmethods_gap,
+    class_T    *extends_cl)
+{
+    for (int loop = 1; loop <= 2; ++loop)
+    {
+       // loop == 1: check class methods
+       // loop == 2: check object methods
+       int extends_method_count = loop == 1
+                               ? extends_cl->class_class_function_count
+                               : extends_cl->class_obj_method_count;
+       if (extends_method_count == 0)
+           continue;
+
+       ufunc_T **extends_methods = loop == 1
+                               ? extends_cl->class_class_functions
+                               : extends_cl->class_obj_methods;
+
+       int method_count = loop == 1 ? classmethods_gap->ga_len
+                                               : objmethods_gap->ga_len;
+       ufunc_T **cl_fp = (ufunc_T **)(loop == 1
+                                               ? classmethods_gap->ga_data
+                                               : objmethods_gap->ga_data);
+
+       for (int i = 0; i < extends_method_count; i++)
+       {
+           ufunc_T *uf = extends_methods[i];
+           if ((uf->uf_flags & FC_ABSTRACT) == 0)
+               continue;
+
+           int method_found = FALSE;
+
+           for (int j = 0; j < method_count; j++)
+           {
+               if (STRCMP(uf->uf_name, cl_fp[j]->uf_name) == 0)
+               {
+                   method_found = TRUE;
+                   break;
+               }
+           }
+
+           if (!method_found)
+           {
+               semsg(_(e_abstract_method_str_not_found), uf->uf_name);
+               return FALSE;
+           }
+       }
+    }
+
+    return TRUE;
+}
+
 /*
  * Check the members of the interface class "ifcl" match the class members
  * ("classmembers_gap") and object members ("objmembers_gap") of a class.
@@ -1266,6 +1323,31 @@ early_ret:
            }
        }
 
+       int abstract_method = FALSE;
+       char_u *pa = p;
+       if (checkforcmd(&p, "abstract", 3))
+       {
+           if (STRNCMP(pa, "abstract", 8) != 0)
+           {
+               semsg(_(e_command_cannot_be_shortened_str), pa);
+               break;
+           }
+
+           if (!is_abstract)
+           {
+               semsg(_(e_abstract_method_in_concrete_class), pa);
+               break;
+           }
+
+           abstract_method = TRUE;
+           p = skipwhite(pa + 8);
+           if (STRNCMP(p, "def", 3) != 0 && STRNCMP(p, "static", 6) != 0)
+           {
+               emsg(_(e_abstract_must_be_followed_by_def_or_static));
+               break;
+           }
+       }
+
        int has_static = FALSE;
        char_u *ps = p;
        if (checkforcmd(&p, "static", 4))
@@ -1344,8 +1426,13 @@ early_ret:
            ea.cookie = eap->cookie;
 
            ga_init2(&lines_to_free, sizeof(char_u *), 50);
+           int class_flags;
+           if (is_class)
+               class_flags = abstract_method ? CF_ABSTRACT_METHOD : CF_CLASS;
+           else
+               class_flags = CF_INTERFACE;
            ufunc_T *uf = define_function(&ea, NULL, &lines_to_free,
-                                          is_class ? CF_CLASS : CF_INTERFACE);
+                                                               class_flags);
            ga_clear_strings(&lines_to_free);
 
            if (uf != NULL)
@@ -1353,7 +1440,8 @@ early_ret:
                char_u  *name = uf->uf_name;
                int     is_new = STRNCMP(name, "new", 3) == 0;
 
-               if (is_new && !is_valid_constructor(uf, is_abstract, has_static))
+               if (is_new && !is_valid_constructor(uf, is_abstract,
+                                                               has_static))
                {
                    func_clear_free(uf, FALSE);
                    break;
@@ -1374,6 +1462,9 @@ early_ret:
                    if (is_new)
                        uf->uf_flags |= FC_NEW;
 
+                   if (abstract_method)
+                       uf->uf_flags |= FC_ABSTRACT;
+
                    ((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf;
                    ++fgap->ga_len;
                }
@@ -1430,12 +1521,20 @@ early_ret:
        success = validate_extends_class(extends, &extends_cl);
     VIM_CLEAR(extends);
 
-    // Check the new class members and object members doesn't duplicate the
+    // Check the new class members and object members are not duplicates of the
     // members in the extended class lineage.
     if (success && extends_cl != NULL)
        success = validate_extends_members(&classmembers, &objmembers,
                                                                extends_cl);
 
+    // When extending an abstract class, make sure all the abstract methods in
+    // the parent class are implemented.  If the current class is an abstract
+    // class, then there is no need for this check.
+    if (success && !is_abstract && extends_cl != NULL
+                               && (extends_cl->class_flags & CLASS_ABSTRACT))
+       success = validate_extends_methods(&classfunctions, &objmethods,
+                                                               extends_cl);
+
     class_T **intf_classes = NULL;
 
     // Check all "implements" entries are valid.
@@ -1463,6 +1562,8 @@ early_ret:
            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);