forked from aniani/vim
patch 9.0.1178: a child class cannot override functions from a base class
Problem: A child class cannot override functions from a base class. Solution: Allow overriding and implement "super".
This commit is contained in:
@@ -3432,4 +3432,10 @@ EXTERN char e_class_name_not_found_str[]
|
|||||||
INIT(= N_("E1353: Class name not found: %s"));
|
INIT(= N_("E1353: Class name not found: %s"));
|
||||||
EXTERN char e_cannot_extend_str[]
|
EXTERN char e_cannot_extend_str[]
|
||||||
INIT(= N_("E1354: Cannot extend %s"));
|
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
|
#endif
|
||||||
|
@@ -527,7 +527,10 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);
|
|||||||
#define t_dict_string (static_types[76])
|
#define t_dict_string (static_types[76])
|
||||||
#define t_const_dict_string (static_types[77])
|
#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
|
#ifdef DO_INIT
|
||||||
= {
|
= {
|
||||||
// 0: t_unknown
|
// 0: t_unknown
|
||||||
@@ -685,6 +688,10 @@ EXTERN type_T static_types[78]
|
|||||||
// 76: t_dict_string
|
// 76: t_dict_string
|
||||||
{VAR_DICT, 0, 0, TTFLAG_STATIC, &t_string, NULL},
|
{VAR_DICT, 0, 0, TTFLAG_STATIC, &t_string, NULL},
|
||||||
{VAR_DICT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &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
|
#endif
|
||||||
;
|
;
|
||||||
|
@@ -1465,6 +1465,7 @@ typedef struct {
|
|||||||
#define TTFLAG_NUMBER_OK 0x04 // tt_type is VAR_FLOAT, VAR_NUMBER is OK
|
#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_STATIC 0x08 // one of the static types, e.g. t_any
|
||||||
#define TTFLAG_CONST 0x10 // cannot be changed
|
#define TTFLAG_CONST 0x10 // cannot be changed
|
||||||
|
#define TTFLAG_SUPER 0x20 // object from "super".
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ACCESS_PRIVATE, // read/write only inside th class
|
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
|
typval_T *class_members_tv; // allocated array of class member vals
|
||||||
|
|
||||||
// class functions: "static def SomeMethod()"
|
// 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
|
ufunc_T **class_class_functions; // allocated
|
||||||
|
|
||||||
// object members: "this.varname"
|
// object members: "this.varname"
|
||||||
@@ -1514,7 +1516,8 @@ struct class_S
|
|||||||
ocmember_T *class_obj_members; // allocated
|
ocmember_T *class_obj_members; // allocated
|
||||||
|
|
||||||
// object methods: "def SomeMethod()"
|
// 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
|
ufunc_T **class_obj_methods; // allocated
|
||||||
|
|
||||||
garray_T class_type_list; // used for type pointers
|
garray_T class_type_list; // used for type pointers
|
||||||
|
@@ -817,6 +817,27 @@ def Test_class_extends()
|
|||||||
endclass
|
endclass
|
||||||
END
|
END
|
||||||
v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar')
|
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
|
enddef
|
||||||
|
|
||||||
|
|
||||||
|
@@ -695,6 +695,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 */
|
||||||
|
/**/
|
||||||
|
1178,
|
||||||
/**/
|
/**/
|
||||||
1177,
|
1177,
|
||||||
/**/
|
/**/
|
||||||
|
@@ -487,9 +487,21 @@ early_ret:
|
|||||||
|
|
||||||
if (uf != NULL)
|
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
|
garray_T *fgap = has_static || is_new
|
||||||
? &classfunctions : &objmethods;
|
? &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 (ga_grow(fgap, 1) == OK)
|
||||||
{
|
{
|
||||||
if (is_new)
|
if (is_new)
|
||||||
@@ -793,7 +805,8 @@ early_ret:
|
|||||||
|
|
||||||
if (nf != NULL && ga_grow(&classfunctions, 1) == OK)
|
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;
|
++classfunctions.ga_len;
|
||||||
|
|
||||||
nf->uf_flags |= FC_NEW;
|
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
|
// loop 1: class functions, loop 2: object methods
|
||||||
for (int loop = 1; loop <= 2; ++loop)
|
for (int loop = 1; loop <= 2; ++loop)
|
||||||
{
|
{
|
||||||
@@ -834,26 +848,52 @@ early_ret:
|
|||||||
if (*fup == NULL)
|
if (*fup == NULL)
|
||||||
goto cleanup;
|
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;
|
int skipped = 0;
|
||||||
for (int i = 0; i < parent_count; ++i)
|
for (int i = 0; i < parent_count; ++i)
|
||||||
{
|
{
|
||||||
// Copy functions from the parent. Can't use the same
|
// Copy functions from the parent. Can't use the same
|
||||||
// function, because "uf_class" is different and compilation
|
// function, because "uf_class" is different and compilation
|
||||||
// will have a different result.
|
// 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.
|
// Skip "new" functions. TODO: not all of them.
|
||||||
if (loop == 1 && STRNCMP(
|
if (loop == 1 && STRNCMP(
|
||||||
extends_cl->class_class_functions[i]->uf_name,
|
extends_cl->class_class_functions[i]->uf_name,
|
||||||
"new", 3) == 0)
|
"new", 3) == 0)
|
||||||
++skipped;
|
++skipped;
|
||||||
else
|
else
|
||||||
*fup[i - skipped] = copy_function((loop == 1
|
{
|
||||||
|
ufunc_T *pf = (loop == 1
|
||||||
? extends_cl->class_class_functions
|
? 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;
|
*fcount -= skipped;
|
||||||
|
|
||||||
// Set the class pointer on all the functions and object methods.
|
// Set the class pointer on all the functions and object methods.
|
||||||
|
@@ -43,16 +43,31 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx)
|
|||||||
if (len == 0)
|
if (len == 0)
|
||||||
return FAIL;
|
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 != NULL
|
||||||
&& (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW)))
|
&& (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW)))
|
||||||
{
|
{
|
||||||
|
int is_super = *name == 's';
|
||||||
if (lvar != NULL)
|
if (lvar != NULL)
|
||||||
{
|
{
|
||||||
CLEAR_POINTER(lvar);
|
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)
|
if (cctx->ctx_ufunc->uf_class != NULL)
|
||||||
|
{
|
||||||
lvar->lv_type = &cctx->ctx_ufunc->uf_class->class_object_type;
|
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;
|
return OK;
|
||||||
}
|
}
|
||||||
|
@@ -263,7 +263,21 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
|
|||||||
return FAIL;
|
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;
|
garray_T *instr = &cctx->ctx_instr;
|
||||||
if (instr->ga_len > 0)
|
if (instr->ga_len > 0)
|
||||||
@@ -286,26 +300,28 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
size_t len = name_end - name;
|
size_t len = name_end - name;
|
||||||
|
|
||||||
class_T *cl = (class_T *)type->tt_member;
|
|
||||||
if (*name_end == '(')
|
if (*name_end == '(')
|
||||||
{
|
{
|
||||||
int function_count;
|
int function_count;
|
||||||
|
int child_count;
|
||||||
ufunc_T **functions;
|
ufunc_T **functions;
|
||||||
|
|
||||||
if (type->tt_type == VAR_CLASS)
|
if (type->tt_type == VAR_CLASS)
|
||||||
{
|
{
|
||||||
function_count = cl->class_class_function_count;
|
function_count = cl->class_class_function_count;
|
||||||
|
child_count = cl->class_class_function_count_child;
|
||||||
functions = cl->class_class_functions;
|
functions = cl->class_class_functions;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// type->tt_type == VAR_OBJECT: method call
|
// type->tt_type == VAR_OBJECT: method call
|
||||||
function_count = cl->class_obj_method_count;
|
function_count = cl->class_obj_method_count;
|
||||||
|
child_count = cl->class_obj_method_count_child;
|
||||||
functions = cl->class_obj_methods;
|
functions = cl->class_obj_methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
ufunc_T *ufunc = NULL;
|
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];
|
ufunc_T *fp = functions[i];
|
||||||
// Use a separate pointer to avoid that ASAN complains about
|
// Use a separate pointer to avoid that ASAN complains about
|
||||||
@@ -643,7 +659,17 @@ compile_load(
|
|||||||
if (name == NULL)
|
if (name == NULL)
|
||||||
return FAIL;
|
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);
|
script_autoload(name, FALSE);
|
||||||
res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any);
|
res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any);
|
||||||
|
Reference in New Issue
Block a user