summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2023-01-11 15:59:05 +0000
committerBram Moolenaar <Bram@vim.org>2023-01-11 15:59:05 +0000
commit58b40092e616585a763cf4d214d47ccd9167d6f7 (patch)
treed0b7b8411f134511a3b46792ee016af9f8f60539
parentad15a39fdbde5ef8d4af9e0fca7e7e53b4843270 (diff)
patch 9.0.1178: a child class cannot override functions from a base classv9.0.1178
Problem: A child class cannot override functions from a base class. Solution: Allow overriding and implement "super".
-rw-r--r--src/errors.h6
-rw-r--r--src/globals.h9
-rw-r--r--src/structs.h7
-rw-r--r--src/testdir/test_vim9_class.vim21
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c54
-rw-r--r--src/vim9compile.c19
-rw-r--r--src/vim9expr.c34
8 files changed, 136 insertions, 16 deletions
diff --git a/src/errors.h b/src/errors.h
index d12eb9ff31..6fa1abb787 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3432,4 +3432,10 @@ EXTERN char e_class_name_not_found_str[]
INIT(= N_("E1353: Class name not found: %s"));
EXTERN char e_cannot_extend_str[]
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
diff --git a/src/globals.h b/src/globals.h
index fd5a0cb020..8c3cb62e43 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -527,7 +527,10 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);
#define t_dict_string (static_types[76])
#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
= {
// 0: t_unknown
@@ -685,6 +688,10 @@ EXTERN type_T static_types[78]
// 76: t_dict_string
{VAR_DICT, 0, 0, TTFLAG_STATIC, &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
;
diff --git a/src/structs.h b/src/structs.h
index 08edcf4020..89ed23dbdb 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1465,6 +1465,7 @@ typedef struct {
#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_CONST 0x10 // cannot be changed
+#define TTFLAG_SUPER 0x20 // object from "super".
typedef enum {
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
// 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
// object members: "this.varname"
@@ -1514,7 +1516,8 @@ struct class_S
ocmember_T *class_obj_members; // allocated
// 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
garray_T class_type_list; // used for type pointers
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index c7ee2188af..220cb7578e 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -817,6 +817,27 @@ def Test_class_extends()
endclass
END
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
diff --git a/src/version.c b/src/version.c
index 2565fcdca8..1c8bf60861 100644
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1178,
+/**/
1177,
/**/
1176,
diff --git a/src/vim9class.c b/src/vim9class.c
index 9b1f913345..0970619b91 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -487,9 +487,21 @@ early_ret:
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
? &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 (is_new)
@@ -793,7 +805,8 @@ early_ret:
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;
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
for (int loop = 1; loop <= 2; ++loop)
{
@@ -834,26 +848,52 @@ early_ret:
if (*fup == NULL)
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;
for (int i = 0; i < parent_count; ++i)
{
// Copy functions from the parent. Can't use the same
// function, because "uf_class" is different and compilation
// 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.
if (loop == 1 && STRNCMP(
extends_cl->class_class_functions[i]->uf_name,
"new", 3) == 0)
++skipped;
else
- *fup[i - skipped] = copy_function((loop == 1
+ {
+ ufunc_T *pf = (loop == 1
? 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;
// Set the class pointer on all the functions and object methods.
diff --git a/src/vim9compile.c b/src/vim9compile.c
index d8e4ae6ed5..9fe850775c 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -43,16 +43,31 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx)
if (len == 0)
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->uf_flags & (FC_OBJECT|FC_NEW)))
{
+ int is_super = *name == 's';
if (lvar != NULL)
{
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)
+ {
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;
}
diff --git a/src/vim9expr.c b/src/vim9expr.c
index 7f5f4c8b0d..27f3bc465f 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -263,7 +263,21 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
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;
if (instr->ga_len > 0)
@@ -286,26 +300,28 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
return FAIL;
size_t len = name_end - name;
- class_T *cl = (class_T *)type->tt_member;
if (*name_end == '(')
{
int function_count;
+ int child_count;
ufunc_T **functions;
if (type->tt_type == VAR_CLASS)
{
function_count = cl->class_class_function_count;
+ child_count = cl->class_class_function_count_child;
functions = cl->class_class_functions;
}
else
{
// type->tt_type == VAR_OBJECT: method call
function_count = cl->class_obj_method_count;
+ child_count = cl->class_obj_method_count_child;
functions = cl->class_obj_methods;
}
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];
// Use a separate pointer to avoid that ASAN complains about
@@ -643,7 +659,17 @@ compile_load(
if (name == NULL)
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);
res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any);