mirror of
https://github.com/vim/vim.git
synced 2025-07-25 10:54:51 -04:00
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 <cb@256bit.org> Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
parent
86cfb39030
commit
7bcd25cad3
@ -5850,6 +5850,7 @@ abandon editing.txt /*abandon*
|
|||||||
abbreviations map.txt /*abbreviations*
|
abbreviations map.txt /*abbreviations*
|
||||||
abel.vim syntax.txt /*abel.vim*
|
abel.vim syntax.txt /*abel.vim*
|
||||||
abs() builtin.txt /*abs()*
|
abs() builtin.txt /*abs()*
|
||||||
|
abstract-method vim9class.txt /*abstract-method*
|
||||||
acos() builtin.txt /*acos()*
|
acos() builtin.txt /*acos()*
|
||||||
active-buffer windows.txt /*active-buffer*
|
active-buffer windows.txt /*active-buffer*
|
||||||
ada#Create_Tags() ft_ada.txt /*ada#Create_Tags()*
|
ada#Create_Tags() ft_ada.txt /*ada#Create_Tags()*
|
||||||
|
@ -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
|
An abstract class is defined the same way as a normal class, except that it
|
||||||
does not have any new() method. *E1359*
|
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.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
|
@ -3495,6 +3495,12 @@ EXTERN char e_duplicate_member_str[]
|
|||||||
INIT(= N_("E1369: Duplicate member: %s"));
|
INIT(= N_("E1369: Duplicate member: %s"));
|
||||||
EXTERN char e_cannot_define_new_function_as_static[]
|
EXTERN char e_cannot_define_new_function_as_static[]
|
||||||
INIT(= N_("E1370: Cannot define a \"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[]
|
EXTERN char e_cannot_mix_positional_and_non_positional_str[]
|
||||||
INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s"));
|
INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s"));
|
||||||
EXTERN char e_fmt_arg_nr_unused_str[]
|
EXTERN char e_fmt_arg_nr_unused_str[]
|
||||||
|
@ -1515,6 +1515,7 @@ struct itf2class_S {
|
|||||||
|
|
||||||
#define CLASS_INTERFACE 1
|
#define CLASS_INTERFACE 1
|
||||||
#define CLASS_EXTENDED 2 // another class extends this one
|
#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
|
// "class_T": used for v_class of typval of VAR_CLASS
|
||||||
// Also used for an interface (class_flags has CLASS_INTERFACE).
|
// 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_OBJECT 0x4000 // object method
|
||||||
#define FC_NEW 0x8000 // constructor
|
#define FC_NEW 0x8000 // constructor
|
||||||
|
#define FC_ABSTRACT 0x10000 // abstract method
|
||||||
|
|
||||||
#define MAX_FUNC_ARGS 20 // maximum number of function arguments
|
#define MAX_FUNC_ARGS 20 // maximum number of function arguments
|
||||||
#define VAR_SHORT_LEN 20 // short variable name length
|
#define VAR_SHORT_LEN 20 // short variable name length
|
||||||
|
@ -4473,4 +4473,140 @@ enddef
|
|||||||
" v9.CheckScriptSuccess(lines)
|
" v9.CheckScriptSuccess(lines)
|
||||||
" enddef
|
" 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
|
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
|
||||||
|
@ -5021,6 +5021,7 @@ define_function(
|
|||||||
// Do not define the function when getting the body fails and when
|
// Do not define the function when getting the body fails and when
|
||||||
// skipping.
|
// skipping.
|
||||||
if (((class_flags & CF_INTERFACE) == 0
|
if (((class_flags & CF_INTERFACE) == 0
|
||||||
|
&& (class_flags & CF_ABSTRACT_METHOD) == 0
|
||||||
&& get_function_body(eap, &newlines, line_arg, lines_to_free)
|
&& get_function_body(eap, &newlines, line_arg, lines_to_free)
|
||||||
== FAIL)
|
== FAIL)
|
||||||
|| eap->skip)
|
|| eap->skip)
|
||||||
|
@ -699,6 +699,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1885,
|
||||||
/**/
|
/**/
|
||||||
1884,
|
1884,
|
||||||
/**/
|
/**/
|
||||||
|
@ -2915,5 +2915,6 @@ long elapsed(DWORD start_tick);
|
|||||||
// Flags used by "class_flags" of define_function()
|
// Flags used by "class_flags" of define_function()
|
||||||
#define CF_CLASS 1 // inside a class
|
#define CF_CLASS 1 // inside a class
|
||||||
#define CF_INTERFACE 2 // inside an interface
|
#define CF_INTERFACE 2 // inside an interface
|
||||||
|
#define CF_ABSTRACT_METHOD 4 // inside an abstract class
|
||||||
|
|
||||||
#endif // VIM__H
|
#endif // VIM__H
|
||||||
|
131
src/vim9class.c
131
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);
|
semsg(_(e_class_name_not_found_str), extends_name);
|
||||||
return success;
|
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
|
else
|
||||||
{
|
{
|
||||||
if (tv.v_type != VAR_CLASS
|
class_T *extends_cl = tv.vval.v_class;
|
||||||
|| tv.vval.v_class == NULL
|
++extends_cl->class_refcount;
|
||||||
|| (tv.vval.v_class->class_flags & CLASS_INTERFACE) != 0)
|
*extends_clp = extends_cl;
|
||||||
semsg(_(e_cannot_extend_str), extends_name);
|
success = TRUE;
|
||||||
else
|
|
||||||
{
|
|
||||||
class_T *extends_cl = tv.vval.v_class;
|
|
||||||
++extends_cl->class_refcount;
|
|
||||||
*extends_clp = extends_cl;
|
|
||||||
success = TRUE;
|
|
||||||
}
|
|
||||||
clear_tv(&tv);
|
|
||||||
}
|
}
|
||||||
|
clear_tv(&tv);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@ -391,6 +389,65 @@ validate_extends_members(
|
|||||||
return TRUE;
|
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
|
* Check the members of the interface class "ifcl" match the class members
|
||||||
* ("classmembers_gap") and object members ("objmembers_gap") of a class.
|
* ("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;
|
int has_static = FALSE;
|
||||||
char_u *ps = p;
|
char_u *ps = p;
|
||||||
if (checkforcmd(&p, "static", 4))
|
if (checkforcmd(&p, "static", 4))
|
||||||
@ -1344,8 +1426,13 @@ early_ret:
|
|||||||
ea.cookie = eap->cookie;
|
ea.cookie = eap->cookie;
|
||||||
|
|
||||||
ga_init2(&lines_to_free, sizeof(char_u *), 50);
|
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,
|
ufunc_T *uf = define_function(&ea, NULL, &lines_to_free,
|
||||||
is_class ? CF_CLASS : CF_INTERFACE);
|
class_flags);
|
||||||
ga_clear_strings(&lines_to_free);
|
ga_clear_strings(&lines_to_free);
|
||||||
|
|
||||||
if (uf != NULL)
|
if (uf != NULL)
|
||||||
@ -1353,7 +1440,8 @@ early_ret:
|
|||||||
char_u *name = uf->uf_name;
|
char_u *name = uf->uf_name;
|
||||||
int is_new = STRNCMP(name, "new", 3) == 0;
|
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);
|
func_clear_free(uf, FALSE);
|
||||||
break;
|
break;
|
||||||
@ -1374,6 +1462,9 @@ early_ret:
|
|||||||
if (is_new)
|
if (is_new)
|
||||||
uf->uf_flags |= FC_NEW;
|
uf->uf_flags |= FC_NEW;
|
||||||
|
|
||||||
|
if (abstract_method)
|
||||||
|
uf->uf_flags |= FC_ABSTRACT;
|
||||||
|
|
||||||
((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf;
|
((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf;
|
||||||
++fgap->ga_len;
|
++fgap->ga_len;
|
||||||
}
|
}
|
||||||
@ -1430,12 +1521,20 @@ early_ret:
|
|||||||
success = validate_extends_class(extends, &extends_cl);
|
success = validate_extends_class(extends, &extends_cl);
|
||||||
VIM_CLEAR(extends);
|
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.
|
// members in the extended class lineage.
|
||||||
if (success && extends_cl != NULL)
|
if (success && extends_cl != NULL)
|
||||||
success = validate_extends_members(&classmembers, &objmembers,
|
success = validate_extends_members(&classmembers, &objmembers,
|
||||||
extends_cl);
|
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;
|
class_T **intf_classes = NULL;
|
||||||
|
|
||||||
// Check all "implements" entries are valid.
|
// Check all "implements" entries are valid.
|
||||||
@ -1463,6 +1562,8 @@ early_ret:
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
if (!is_class)
|
if (!is_class)
|
||||||
cl->class_flags = CLASS_INTERFACE;
|
cl->class_flags = CLASS_INTERFACE;
|
||||||
|
else if (is_abstract)
|
||||||
|
cl->class_flags = CLASS_ABSTRACT;
|
||||||
|
|
||||||
cl->class_refcount = 1;
|
cl->class_refcount = 1;
|
||||||
cl->class_name = vim_strnsave(name_start, name_end - name_start);
|
cl->class_name = vim_strnsave(name_start, name_end - name_start);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user