Patch 9.0.1159

5 views
Skip to first unread message

Bram Moolenaar

unread,
Jan 8, 2023, 2:54:51 PM1/8/23
to vim...@googlegroups.com

Patch 9.0.1159
Problem: Extends argument for class not implemented yet.
Solution: Basic implementation of "extends".
Files: src/errors.h, src/vim9class.c, src/structs.h, src/userfunc.c,
src/proto/userfunc.pro, src/testdir/test_vim9_class.vim


*** ../vim-9.0.1158/src/errors.h 2023-01-07 14:50:59.044362174 +0000
--- src/errors.h 2023-01-08 14:21:50.948883266 +0000
***************
*** 3426,3429 ****
--- 3426,3435 ----
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
*** ../vim-9.0.1158/src/vim9class.c 2023-01-07 14:50:59.044362174 +0000
--- src/vim9class.c 2023-01-08 19:01:34.290240107 +0000
***************
*** 160,165 ****
--- 160,167 ----
/*
* 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,181 ****
static int
add_members_to_class(
garray_T *gap,
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)
return FAIL;
if (gap->ga_len > 0)
! mch_memmove(*members, gap->ga_data, sizeof(ocmember_T) * gap->ga_len);
VIM_CLEAR(gap->ga_data);
return OK;
}
--- 169,196 ----
static int
add_members_to_class(
garray_T *gap,
+ ocmember_T *parent_members,
+ int parent_count,
ocmember_T **members,
int *member_count)
{
! *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)
! // 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,238 ****
--- 248,256 ----
// 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,249 ****
while (*arg != NUL && *arg != '#' && *arg != '\n')
{
// TODO:
- // extends SomeClass
// specifies SomeInterface
! if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10]))
{
if (ga_impl.ga_len > 0)
{
--- 259,287 ----
while (*arg != NUL && *arg != '#' && *arg != '\n')
{
// TODO:
// specifies SomeInterface
! 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,294 ****
--- 327,333 ----
{
semsg(_(e_trailing_characters_str), arg);
early_ret:
+ vim_free(extends);
ga_clear_strings(&ga_impl);
return;
}
***************
*** 496,512 ****
}
vim_free(theline);

! // Check a few things before defining the class.
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)
{
semsg(_(e_interface_name_not_found_str), impl);
success = FALSE;
--- 535,584 ----
}
vim_free(theline);

! 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)
{
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_IMPORT) == FAIL)
{
semsg(_(e_interface_name_not_found_str), impl);
success = FALSE;
***************
*** 620,625 ****
--- 692,699 ----
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,645 ****

// Add class and object members to "cl".
if (add_members_to_class(&classmembers,
! &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)
goto cleanup;

if (is_class && cl->class_class_member_count > 0)
--- 709,727 ----

// Add class and object members to "cl".
if (add_members_to_class(&classmembers,
! 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,
! 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,754 ****
ufunc_T ***fup = loop == 1 ? &cl->class_class_functions
: &cl->class_obj_methods;

! *fcount = gap->ga_len;
! if (gap->ga_len == 0)
{
*fup = NULL;
continue;
}
! *fup = ALLOC_MULT(ufunc_T *, gap->ga_len);
if (*fup == NULL)
goto cleanup;
! mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len);
vim_free(gap->ga_data);

! // Set the class pointer on all the object methods.
! for (int i = 0; i < gap->ga_len; ++i)
{
ufunc_T *fp = (*fup)[i];
fp->uf_class = cl;
--- 817,863 ----
ufunc_T ***fup = loop == 1 ? &cl->class_class_functions
: &cl->class_obj_methods;

! 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 *, *fcount);
if (*fup == NULL)
goto cleanup;
!
! 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 functions and object methods.
! for (int i = 0; i < *fcount; ++i)
{
ufunc_T *fp = (*fup)[i];
fp->uf_class = cl;
***************
*** 786,791 ****
--- 895,902 ----
vim_free(cl);
}

+ vim_free(extends);
+ class_unref(extends_cl);
ga_clear_strings(&ga_impl);

for (int round = 1; round <= 2; ++round)
***************
*** 1167,1172 ****
--- 1278,1285 ----
// 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);
*** ../vim-9.0.1158/src/structs.h 2023-01-06 18:42:16.434674109 +0000
--- src/structs.h 2023-01-08 14:41:24.731883105 +0000
***************
*** 1494,1499 ****
--- 1494,1501 ----
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
*** ../vim-9.0.1158/src/userfunc.c 2023-01-05 19:59:14.003418087 +0000
--- src/userfunc.c 2023-01-08 19:47:46.187923640 +0000
***************
*** 5516,5521 ****
--- 5516,5589 ----
}

/*
+ * 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
*** ../vim-9.0.1158/src/proto/userfunc.pro 2023-01-05 19:59:14.003418087 +0000
--- src/proto/userfunc.pro 2023-01-08 15:45:23.169463349 +0000
***************
*** 56,61 ****
--- 56,62 ----
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);
*** ../vim-9.0.1158/src/testdir/test_vim9_class.vim 2023-01-07 14:50:59.044362174 +0000
--- src/testdir/test_vim9_class.vim 2023-01-08 15:18:58.039817720 +0000
***************
*** 753,757 ****
--- 753,823 ----
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
*** ../vim-9.0.1158/src/version.c 2023-01-08 13:44:21.073352325 +0000
--- src/version.c 2023-01-08 14:11:10.173525123 +0000
***************
*** 697,698 ****
--- 697,700 ----
{ /* Add new patch number below this line */
+ /**/
+ 1159,
/**/

--
"Beware of bugs in the above code; I have only proved
it correct, not tried it." -- Donald Knuth

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
Reply all
Reply to author
Forward
0 new messages