summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2023-09-08 19:27:51 +0200
committerChristian Brabandt <cb@256bit.org>2023-09-08 19:29:31 +0200
commit7bcd25cad3e9d5c9e25c7ae2bde67285c26f73cd (patch)
tree9834383bcf8ab51010fa003c72eb35bfa3d6f4ba
parent86cfb39030eb557e1a1c7804f9c147556ca5dbf1 (diff)
patch 9.0.1885: Vim9: no support for abstract methodsv9.0.1885
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>
-rw-r--r--runtime/doc/tags1
-rw-r--r--runtime/doc/vim9class.txt10
-rw-r--r--src/errors.h6
-rw-r--r--src/structs.h2
-rw-r--r--src/testdir/test_vim9_class.vim136
-rw-r--r--src/userfunc.c1
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h1
-rw-r--r--src/vim9class.c131
9 files changed, 275 insertions, 15 deletions
diff --git a/runtime/doc/tags b/runtime/doc/tags
index ae893292a8..6c32c96cb8 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -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()*
diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt
index 8a9e37e812..20ad4bbab5 100644
--- a/runtime/doc/vim9class.txt
+++ b/runtime/doc/vim9class.txt
@@ -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.
==============================================================================
diff --git a/src/errors.h b/src/errors.h
index fc40fe865f..57769062b1 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -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[]
diff --git a/src/structs.h b/src/structs.h
index 7acd857450..9214096973 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index a8c95a74da..9f1e91d86c 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -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
diff --git a/src/userfunc.c b/src/userfunc.c
index 6638849dbb..efde29dcb1 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -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)
diff --git a/src/version.c b/src/version.c
index 9797b234e7..8faa534b21 100644
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1885,
+/**/
1884,
/**/
1883,
diff --git a/src/vim.h b/src/vim.h
index 265fd738e2..6b05a2ae67 100644
--- 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
diff --git a/src/vim9class.c b/src/vim9class.c
index 9328ee713c..8e2c88b14d 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -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;
}
@@ -392,6 +390,65 @@ validate_extends_members(
}
/*
+ * 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.
* Returns TRUE if the class and object member names are valid.
@@ -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);