# HG changeset patch # User Christian Brabandt # Date 1694195103 -7200 # Node ID 499ba27ba0f6a5c269c4520221899c5f809d20d8 # Parent d77a9aab91ad70e344bcdeb8c913536742b01de0 patch 9.0.1885: Vim9: no support for abstract methods Commit: https://github.com/vim/vim/commit/7bcd25cad3e9d5c9e25c7ae2bde67285c26f73cd Author: Yegappan Lakshmanan Date: Fri Sep 8 19:27:51 2023 +0200 patch 9.0.1885: Vim9: no support for abstract methods 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 Co-authored-by: Yegappan Lakshmanan diff --git a/runtime/doc/tags b/runtime/doc/tags --- 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 --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -358,6 +358,16 @@ class, for which objects can be created. 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 --- 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 --- 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 --- 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 + endclass + class B extends A + def Foo(a: number, b: string): list + return [] + enddef + endclass + END + v9.CheckScriptFailure(lines, 'E1407: Member "Foo": type mismatch, expected func(string, number): list but got func(number, string): list') + + # 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 + endclass + class B extends A + def Foo(): list + 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 --- 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 --- 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 --- 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 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -308,21 +308,19 @@ validate_extends_class(char_u *extends_n 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);