summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2023-01-08 19:54:10 +0000
committerBram Moolenaar <Bram@vim.org>2023-01-08 19:54:10 +0000
commit8367716a6e9589d61a771e6c329da05c9b55e61a (patch)
tree204db43661aad4e932fe0475a302378778457bf0
parent7f8b2559a30e2e2a443c35b28e94c6b45ba7ae04 (diff)
downloadvim-git-9.0.1159.tar.gz
patch 9.0.1159: extends argument for class not implemented yetv9.0.1159
Problem: Extends argument for class not implemented yet. Solution: Basic implementation of "extends".
-rw-r--r--src/errors.h6
-rw-r--r--src/proto/userfunc.pro1
-rw-r--r--src/structs.h2
-rw-r--r--src/testdir/test_vim9_class.vim66
-rw-r--r--src/userfunc.c68
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c153
7 files changed, 278 insertions, 20 deletions
diff --git a/src/errors.h b/src/errors.h
index 5a59f7fbc..d12eb9ff3 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3426,4 +3426,10 @@ EXTERN char e_duplicate_implements[]
INIT(= N_("E1350: Duplicate \"implements\""));
EXTERN char e_duplicate_interface_after_implements_str[]
INIT(= N_("E1351: Duplicate interface after \"implements\": %s"));
+EXTERN char e_duplicate_extends[]
+ INIT(= N_("E1352: Duplicate \"extends\""));
+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"));
#endif
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index a87113faa..94b53f676 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -56,6 +56,7 @@ int has_varargs(ufunc_T *ufunc);
int function_exists(char_u *name, int no_deref);
char_u *get_expanded_name(char_u *name, int check);
char_u *get_user_func_name(expand_T *xp, int idx);
+ufunc_T *copy_function(ufunc_T *fp);
void ex_delfunction(exarg_T *eap);
void func_unref(char_u *name);
void func_ptr_unref(ufunc_T *fp);
diff --git a/src/structs.h b/src/structs.h
index 78108c18f..08edcf402 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1494,6 +1494,8 @@ struct class_S
int class_refcount;
int class_copyID; // used by garbage collection
+ class_T *class_extends; // parent class or NULL
+
// interfaces declared for the class
int class_interface_count;
char_u **class_interfaces; // allocated array of names
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index ef9ef5562..c7ee2188a 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -753,5 +753,71 @@ def Test_class_used_as_type()
v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object but got string')
enddef
+def Test_class_extends()
+ var lines =<< trim END
+ vim9script
+ class Base
+ this.one = 1
+ def GetOne(): number
+ return this.one
+ enddef
+ endclass
+ class Child extends Base
+ this.two = 2
+ def GetTotal(): number
+ return this.one + this.two
+ enddef
+ endclass
+ var o = Child.new()
+ assert_equal(1, o.one)
+ assert_equal(2, o.two)
+ assert_equal(1, o.GetOne())
+ assert_equal(3, o.GetTotal())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.one = 1
+ endclass
+ class Child extends Base
+ this.two = 2
+ endclass
+ var o = Child.new(3, 44)
+ assert_equal(3, o.one)
+ assert_equal(44, o.two)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.one = 1
+ endclass
+ class Child extends Base extends Base
+ this.two = 2
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1352: Duplicate "extends"')
+
+ lines =<< trim END
+ vim9script
+ class Child extends BaseClass
+ this.two = 2
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1353: Class name not found: BaseClass')
+
+ lines =<< trim END
+ vim9script
+ var SomeVar = 99
+ class Child extends SomeVar
+ this.two = 2
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar')
+enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/userfunc.c b/src/userfunc.c
index 032c99cff..9ac3a4426 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -5516,6 +5516,74 @@ get_user_func_name(expand_T *xp, int idx)
}
/*
+ * Make a copy of a function.
+ * Intended to be used for a function defined on a base class that has a copy
+ * on the child class.
+ * The copy has uf_refcount set to one.
+ * Returns NULL when out of memory.
+ */
+ ufunc_T *
+copy_function(ufunc_T *fp)
+{
+ // The struct may have padding, make sure we allocate at least the size of
+ // the struct.
+ size_t len = offsetof(ufunc_T, uf_name) + STRLEN(fp->uf_name) + 1;
+ ufunc_T *ufunc = alloc_clear(len < sizeof(ufunc_T) ? sizeof(ufunc_T) : len);
+ if (ufunc == NULL)
+ return NULL;
+
+ // Most things can just be copied.
+ *ufunc = *fp;
+
+ ufunc->uf_def_status = UF_TO_BE_COMPILED;
+ ufunc->uf_dfunc_idx = 0;
+ ufunc->uf_class = NULL;
+
+ ga_copy_strings(&fp->uf_args, &ufunc->uf_args);
+ ga_copy_strings(&fp->uf_def_args, &ufunc->uf_def_args);
+
+ if (ufunc->uf_arg_types != NULL)
+ {
+ // "uf_arg_types" is an allocated array, make a copy.
+ type_T **at = ALLOC_CLEAR_MULT(type_T *, ufunc->uf_args.ga_len);
+ if (at != NULL)
+ {
+ mch_memmove(at, ufunc->uf_arg_types,
+ sizeof(type_T *) * ufunc->uf_args.ga_len);
+ ufunc->uf_arg_types = at;
+ }
+ }
+
+ // TODO: how about the types themselves? they can be freed when the
+ // original function is freed:
+ // type_T **uf_arg_types;
+ // type_T *uf_ret_type;
+
+ ufunc->uf_type_list.ga_len = 0;
+ ufunc->uf_type_list.ga_data = NULL;
+
+ // TODO: partial_T *uf_partial;
+
+ if (ufunc->uf_va_name != NULL)
+ ufunc->uf_va_name = vim_strsave(ufunc->uf_va_name);
+
+ // TODO:
+ // type_T *uf_va_type;
+ // type_T *uf_func_type;
+
+ ufunc->uf_block_depth = 0;
+ ufunc->uf_block_ids = NULL;
+
+ ga_copy_strings(&fp->uf_lines, &ufunc->uf_lines);
+
+ ufunc->uf_refcount = 1;
+ ufunc->uf_name_exp = NULL;
+ STRCPY(ufunc->uf_name, fp->uf_name);
+
+ return ufunc;
+}
+
+/*
* ":delfunction {name}"
*/
void
diff --git a/src/version.c b/src/version.c
index 560fb9d00..a9a54048d 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 */
/**/
+ 1159,
+/**/
1158,
/**/
1157,
diff --git a/src/vim9class.c b/src/vim9class.c
index 4a8dd7c0e..9b1f91334 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -160,6 +160,8 @@ add_member(
/*
* Move the class or object members found while parsing a class into the class.
* "gap" contains the found members.
+ * "parent_members" points to the members in the parent class (if any)
+ * "parent_count" is the number of members in the parent class
* "members" will be set to the newly allocated array of members and
* "member_count" set to the number of members.
* Returns OK or FAIL.
@@ -167,15 +169,28 @@ add_member(
static int
add_members_to_class(
garray_T *gap,
+ ocmember_T *parent_members,
+ int parent_count,
ocmember_T **members,
int *member_count)
{
- *member_count = gap->ga_len;
- *members = gap->ga_len == 0 ? NULL : ALLOC_MULT(ocmember_T, gap->ga_len);
- if (gap->ga_len > 0 && *members == NULL)
+ *member_count = parent_count + gap->ga_len;
+ *members = *member_count == 0 ? NULL
+ : ALLOC_MULT(ocmember_T, *member_count);
+ if (*member_count > 0 && *members == NULL)
return FAIL;
+ for (int i = 0; i < parent_count; ++i)
+ {
+ // parent members need to be copied
+ *members[i] = parent_members[i];
+ members[i]->ocm_name = vim_strsave(members[i]->ocm_name);
+ if (members[i]->ocm_init != NULL)
+ members[i]->ocm_init = vim_strsave(members[i]->ocm_init);
+ }
if (gap->ga_len > 0)
- mch_memmove(*members, gap->ga_data, sizeof(ocmember_T) * gap->ga_len);
+ // new members are moved
+ mch_memmove(*members + parent_count,
+ gap->ga_data, sizeof(ocmember_T) * gap->ga_len);
VIM_CLEAR(gap->ga_data);
return OK;
}
@@ -233,6 +248,9 @@ ex_class(exarg_T *eap)
// generics: <Tkey, Tentry>
// handle "is_export" if it is set
+ // Name for "extends BaseClass"
+ char_u *extends = NULL;
+
// Names for "implements SomeInterface"
garray_T ga_impl;
ga_init2(&ga_impl, sizeof(char_u *), 5);
@@ -241,9 +259,29 @@ ex_class(exarg_T *eap)
while (*arg != NUL && *arg != '#' && *arg != '\n')
{
// TODO:
- // extends SomeClass
// specifies SomeInterface
- if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10]))
+ if (STRNCMP(arg, "extends", 7) == 0 && IS_WHITE_OR_NUL(arg[7]))
+ {
+ if (extends != NULL)
+ {
+ emsg(_(e_duplicate_extends));
+ goto early_ret;
+ }
+ arg = skipwhite(arg + 7);
+ char_u *end = find_name_end(arg, NULL, NULL, FNE_CHECK_START);
+ if (!IS_WHITE_OR_NUL(*end))
+ {
+ semsg(_(e_white_space_required_after_name_str), arg);
+ goto early_ret;
+ }
+ extends = vim_strnsave(arg, end - arg);
+ if (extends == NULL)
+ goto early_ret;
+
+ arg = skipwhite(end + 1);
+ }
+ else if (STRNCMP(arg, "implements", 10) == 0
+ && IS_WHITE_OR_NUL(arg[10]))
{
if (ga_impl.ga_len > 0)
{
@@ -289,6 +327,7 @@ ex_class(exarg_T *eap)
{
semsg(_(e_trailing_characters_str), arg);
early_ret:
+ vim_free(extends);
ga_clear_strings(&ga_impl);
return;
}
@@ -496,17 +535,50 @@ early_ret:
}
vim_free(theline);
- // Check a few things before defining the class.
+ class_T *extends_cl = NULL; // class from "extends" argument
+
+ /*
+ * Check a few things before defining the class.
+ */
+
+ // Check the "extends" class is valid.
+ if (success && extends != NULL)
+ {
+ typval_T tv;
+ tv.v_type = VAR_UNKNOWN;
+ if (eval_variable(extends, 0, 0, &tv, NULL, EVAL_VAR_IMPORT) == FAIL)
+ {
+ semsg(_(e_class_name_not_found_str), extends);
+ success = FALSE;
+ }
+ 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);
+ success = FALSE;
+ }
+ else
+ {
+ extends_cl = tv.vval.v_class;
+ ++extends_cl->class_refcount;
+ }
+ clear_tv(&tv);
+ }
+ }
+ VIM_CLEAR(extends);
+
+ // Check all "implements" entries are valid.
if (success && ga_impl.ga_len > 0)
{
- // Check all "implements" entries are valid and correct.
for (int i = 0; i < ga_impl.ga_len && success; ++i)
{
char_u *impl = ((char_u **)ga_impl.ga_data)[i];
typval_T tv;
tv.v_type = VAR_UNKNOWN;
- if (eval_variable(impl, 0, 0, &tv, NULL,
- EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT) == FAIL)
+ if (eval_variable(impl, 0, 0, &tv, NULL, EVAL_VAR_IMPORT) == FAIL)
{
semsg(_(e_interface_name_not_found_str), impl);
success = FALSE;
@@ -620,6 +692,8 @@ early_ret:
if (cl->class_name == NULL)
goto cleanup;
+ cl->class_extends = extends_cl;
+
if (ga_impl.ga_len > 0)
{
// Move the "implements" names into the class.
@@ -635,11 +709,19 @@ early_ret:
// Add class and object members to "cl".
if (add_members_to_class(&classmembers,
- &cl->class_class_members,
- &cl->class_class_member_count) == FAIL
+ extends_cl == NULL ? NULL
+ : extends_cl->class_class_members,
+ extends_cl == NULL ? 0
+ : extends_cl->class_class_member_count,
+ &cl->class_class_members,
+ &cl->class_class_member_count) == FAIL
|| add_members_to_class(&objmembers,
- &cl->class_obj_members,
- &cl->class_obj_member_count) == FAIL)
+ extends_cl == NULL ? NULL
+ : extends_cl->class_obj_members,
+ extends_cl == NULL ? 0
+ : extends_cl->class_obj_member_count,
+ &cl->class_obj_members,
+ &cl->class_obj_member_count) == FAIL)
goto cleanup;
if (is_class && cl->class_class_member_count > 0)
@@ -735,20 +817,47 @@ early_ret:
ufunc_T ***fup = loop == 1 ? &cl->class_class_functions
: &cl->class_obj_methods;
- *fcount = gap->ga_len;
- if (gap->ga_len == 0)
+ int parent_count = 0;
+ if (extends_cl != NULL)
+ // Include functions from the parent.
+ parent_count = loop == 1
+ ? extends_cl->class_class_function_count
+ : extends_cl->class_obj_method_count;
+
+ *fcount = parent_count + gap->ga_len;
+ if (*fcount == 0)
{
*fup = NULL;
continue;
}
- *fup = ALLOC_MULT(ufunc_T *, gap->ga_len);
+ *fup = ALLOC_MULT(ufunc_T *, *fcount);
if (*fup == NULL)
goto cleanup;
- mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * 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.
+ // 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
+ ? extends_cl->class_class_functions
+ : extends_cl->class_obj_methods)[i]);
+ }
+
+ 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 object methods.
- for (int i = 0; i < gap->ga_len; ++i)
+ // Set the class pointer on all the functions and object methods.
+ for (int i = 0; i < *fcount; ++i)
{
ufunc_T *fp = (*fup)[i];
fp->uf_class = cl;
@@ -786,6 +895,8 @@ cleanup:
vim_free(cl);
}
+ vim_free(extends);
+ class_unref(extends_cl);
ga_clear_strings(&ga_impl);
for (int round = 1; round <= 2; ++round)
@@ -1167,6 +1278,8 @@ class_unref(class_T *cl)
// be freed.
VIM_CLEAR(cl->class_name);
+ class_unref(cl->class_extends);
+
for (int i = 0; i < cl->class_interface_count; ++i)
vim_free(((char_u **)cl->class_interfaces)[i]);
vim_free(cl->class_interfaces);