Patch 9.0.1152
Problem: Class "implements" argument not implemented.
Solution: Implement "implements" argument. Add basic checks for when a
class implements an interface.
Files: src/vim9class.c, src/alloc.c, src/errors.h, src/evalvars.c,
src/structs.h, src/eval.c, src/testdir/test_vim9_class.vim
*** ../vim-9.0.1151/src/vim9class.c 2023-01-05 19:59:14.003418087 +0000
--- src/vim9class.c 2023-01-06 18:33:06.526908945 +0000
***************
*** 227,241 ****
semsg(_(e_white_space_required_after_name_str), arg);
return;
}
// TODO:
// generics: <Tkey, Tentry>
- // extends SomeClass
- // implements SomeInterface
- // specifies SomeInterface
- // check that nothing follows
// handle "is_export" if it is set
garray_T type_list; // list of pointers to allocated types
ga_init2(&type_list, sizeof(type_T *), 10);
--- 227,276 ----
semsg(_(e_white_space_required_after_name_str), arg);
return;
}
+ char_u *name_start = arg;
// TODO:
// generics: <Tkey, Tentry>
// handle "is_export" if it is set
+ // Names for "implements SomeInterface"
+ garray_T ga_impl;
+ ga_init2(&ga_impl, sizeof(char_u *), 5);
+
+ arg = skipwhite(name_end);
+ while (*arg != NUL && *arg != '#' && *arg != '\n')
+ {
+ // TODO:
+ // extends SomeClass
+ // specifies SomeInterface
+ if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10]))
+ {
+ arg = skipwhite(arg + 10);
+ char_u *impl_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START);
+ if (!IS_WHITE_OR_NUL(*impl_end))
+ {
+ semsg(_(e_white_space_required_after_name_str), arg);
+ goto early_ret;
+ }
+ char_u *iname = vim_strnsave(arg, impl_end - arg);
+ if (iname == NULL)
+ goto early_ret;
+ if (ga_add_string(&ga_impl, iname) == FAIL)
+ {
+ vim_free(iname);
+ goto early_ret;
+ }
+ arg = skipwhite(impl_end);
+ }
+ else
+ {
+ semsg(_(e_trailing_characters_str), arg);
+ early_ret:
+ ga_clear_strings(&ga_impl);
+ return;
+ }
+ }
+
garray_T type_list; // list of pointers to allocated types
ga_init2(&type_list, sizeof(type_T *), 10);
***************
*** 438,443 ****
--- 473,586 ----
}
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;
+ break;
+ }
+
+ if (tv.v_type != VAR_CLASS
+ || tv.vval.v_class == NULL
+ || (tv.vval.v_class->class_flags & CLASS_INTERFACE) == 0)
+ {
+ semsg(_(e_not_valid_interface_str), impl);
+ success = FALSE;
+ }
+
+ // check the members of the interface match the members of the class
+ class_T *ifcl = tv.vval.v_class;
+ for (int loop = 1; loop <= 2 && success; ++loop)
+ {
+ // loop == 1: check class members
+ // loop == 2: check object members
+ int if_count = loop == 1 ? ifcl->class_class_member_count
+ : ifcl->class_obj_member_count;
+ if (if_count == 0)
+ continue;
+ ocmember_T *if_ms = loop == 1 ? ifcl->class_class_members
+ : ifcl->class_obj_members;
+ ocmember_T *cl_ms = (ocmember_T *)(loop == 1
+ ? classmembers.ga_data
+ : objmembers.ga_data);
+ int cl_count = loop == 1 ? classmembers.ga_len
+ : objmembers.ga_len;
+ for (int if_i = 0; if_i < if_count; ++if_i)
+ {
+ int cl_i;
+ for (cl_i = 0; cl_i < cl_count; ++cl_i)
+ {
+ ocmember_T *m = &cl_ms[cl_i];
+ if (STRCMP(if_ms[if_i].ocm_name, m->ocm_name) == 0)
+ {
+ // TODO: check type
+ break;
+ }
+ }
+ if (cl_i == cl_count)
+ {
+ semsg(_(e_member_str_of_interface_str_not_implemented),
+ if_ms[if_i].ocm_name, impl);
+ success = FALSE;
+ break;
+ }
+ }
+ }
+
+ // check the functions/methods of the interface match the
+ // functions/methods of the class
+ for (int loop = 1; loop <= 2 && success; ++loop)
+ {
+ // loop == 1: check class functions
+ // loop == 2: check object methods
+ int if_count = loop == 1 ? ifcl->class_class_function_count
+ : ifcl->class_obj_method_count;
+ if (if_count == 0)
+ continue;
+ ufunc_T **if_fp = loop == 1 ? ifcl->class_class_functions
+ : ifcl->class_obj_methods;
+ ufunc_T **cl_fp = (ufunc_T **)(loop == 1
+ ? classfunctions.ga_data
+ : objmethods.ga_data);
+ int cl_count = loop == 1 ? classfunctions.ga_len
+ : objmethods.ga_len;
+ for (int if_i = 0; if_i < if_count; ++if_i)
+ {
+ char_u *if_name = if_fp[if_i]->uf_name;
+ int cl_i;
+ for (cl_i = 0; cl_i < cl_count; ++cl_i)
+ {
+ char_u *cl_name = cl_fp[cl_i]->uf_name;
+ if (STRCMP(if_name, cl_name) == 0)
+ {
+ // TODO: check return and argument types
+ break;
+ }
+ }
+ if (cl_i == cl_count)
+ {
+ semsg(_(e_function_str_of_interface_str_not_implemented),
+ if_name, impl);
+ success = FALSE;
+ break;
+ }
+ }
+ }
+
+ clear_tv(&tv);
+ }
+ }
+
class_T *cl = NULL;
if (success)
{
***************
*** 450,459 ****
cl->class_flags = CLASS_INTERFACE;
cl->class_refcount = 1;
! cl->class_name = vim_strnsave(arg, name_end - arg);
if (cl->class_name == NULL)
goto cleanup;
// Add class and object members to "cl".
if (add_members_to_class(&classmembers,
&cl->class_class_members,
--- 593,615 ----
cl->class_flags = CLASS_INTERFACE;
cl->class_refcount = 1;
! cl->class_name = vim_strnsave(name_start, name_end - name_start);
if (cl->class_name == NULL)
goto cleanup;
+ if (ga_impl.ga_len > 0)
+ {
+ // Move the "implements" names into the class.
+ cl->class_interface_count = ga_impl.ga_len;
+ cl->class_interfaces = ALLOC_MULT(char_u *, ga_impl.ga_len);
+ if (cl->class_interfaces == NULL)
+ goto cleanup;
+ for (int i = 0; i < ga_impl.ga_len; ++i)
+ cl->class_interfaces[i] = ((char_u **)ga_impl.ga_data)[i];
+ CLEAR_POINTER(ga_impl.ga_data);
+ ga_impl.ga_len = 0;
+ }
+
// Add class and object members to "cl".
if (add_members_to_class(&classmembers,
&cl->class_class_members,
***************
*** 499,505 ****
have_new = TRUE;
break;
}
! if (!have_new)
{
// No new() method was defined, add the default constructor.
garray_T fga;
--- 655,661 ----
have_new = TRUE;
break;
}
! if (is_class && !have_new)
{
// No new() method was defined, add the default constructor.
garray_T fga;
***************
*** 589,594 ****
--- 745,751 ----
// - Fill hashtab with object members and methods ?
// Add the class to the script-local variables.
+ // TODO: handle other context, e.g. in a function
typval_T tv;
tv.v_type = VAR_CLASS;
tv.vval.v_class = cl;
***************
*** 607,612 ****
--- 764,771 ----
vim_free(cl);
}
+ ga_clear_strings(&ga_impl);
+
for (int round = 1; round <= 2; ++round)
{
garray_T *gap = round == 1 ? &classmembers : &objmembers;
***************
*** 986,991 ****
--- 1145,1154 ----
// be freed.
VIM_CLEAR(cl->class_name);
+ for (int i = 0; i < cl->class_interface_count; ++i)
+ vim_free(((char_u **)cl->class_interfaces)[i]);
+ vim_free(cl->class_interfaces);
+
for (int i = 0; i < cl->class_class_member_count; ++i)
{
ocmember_T *m = &cl->class_class_members[i];
*** ../vim-9.0.1151/src/alloc.c 2022-10-14 13:11:10.128828896 +0100
--- src/alloc.c 2023-01-06 14:30:22.030967550 +0000
***************
*** 813,819 ****
/*
* Add string "p" to "gap".
! * When out of memory "p" is freed and FAIL is returned.
*/
int
ga_add_string(garray_T *gap, char_u *p)
--- 813,819 ----
/*
* Add string "p" to "gap".
! * When out of memory FAIL is returned (caller may want to free "p").
*/
int
ga_add_string(garray_T *gap, char_u *p)
*** ../vim-9.0.1151/src/errors.h 2023-01-05 19:59:14.003418087 +0000
--- src/errors.h 2023-01-06 18:32:59.238924811 +0000
***************
*** 3414,3417 ****
--- 3414,3425 ----
INIT(= N_("E1344: Cannot initialize a member in an interface"));
EXTERN char e_not_valid_command_in_interface_str[]
INIT(= N_("E1345: Not a valid command in an interface: %s"));
+ EXTERN char e_interface_name_not_found_str[]
+ INIT(= N_("E1346: Interface name not found: %s"));
+ EXTERN char e_not_valid_interface_str[]
+ INIT(= N_("E1347: Not a valid interface: %s"));
+ EXTERN char e_member_str_of_interface_str_not_implemented[]
+ INIT(= N_("E1348: Member \"%s\" of interface \"%s\" not implemented"));
+ EXTERN char e_function_str_of_interface_str_not_implemented[]
+ INIT(= N_("E1349: Function \"%s\" of interface \"%s\" not implemented"));
#endif
*** ../vim-9.0.1151/src/evalvars.c 2023-01-03 10:54:03.665703997 +0000
--- src/evalvars.c 2023-01-06 15:36:26.422097646 +0000
***************
*** 2913,2919 ****
int
eval_variable(
char_u *name,
! int len, // length of "name"
scid_T sid, // script ID for imported item or zero
typval_T *rettv, // NULL when only checking existence
dictitem_T **dip, // non-NULL when typval's dict item is needed
--- 2913,2919 ----
int
eval_variable(
char_u *name,
! int len, // length of "name" or zero
scid_T sid, // script ID for imported item or zero
typval_T *rettv, // NULL when only checking existence
dictitem_T **dip, // non-NULL when typval's dict item is needed
***************
*** 2923,2934 ****
typval_T *tv = NULL;
int found = FALSE;
hashtab_T *ht = NULL;
! int cc;
type_T *type = NULL;
! // truncate the name, so that we can use strcmp()
! cc = name[len];
! name[len] = NUL;
// Check for local variable when debugging.
if ((tv = lookup_debug_var(name)) == NULL)
--- 2923,2937 ----
typval_T *tv = NULL;
int found = FALSE;
hashtab_T *ht = NULL;
! int cc = 0;
type_T *type = NULL;
! if (len > 0)
! {
! // truncate the name, so that we can use strcmp()
! cc = name[len];
! name[len] = NUL;
! }
// Check for local variable when debugging.
if ((tv = lookup_debug_var(name)) == NULL)
***************
*** 3095,3101 ****
}
}
! name[len] = cc;
return ret;
}
--- 3098,3105 ----
}
}
! if (len > 0)
! name[len] = cc;
return ret;
}
*** ../vim-9.0.1151/src/structs.h 2023-01-05 20:14:39.259710536 +0000
--- src/structs.h 2023-01-06 16:07:00.287262332 +0000
***************
*** 1494,1499 ****
--- 1494,1503 ----
int class_refcount;
int class_copyID; // used by garbage collection
+ // interfaces declared for the class
+ int class_interface_count;
+ char_u **class_interfaces; // allocated array of names
+
// class members: "static varname"
int class_class_member_count;
ocmember_T *class_class_members; // allocated
*** ../vim-9.0.1151/src/eval.c 2023-01-05 13:16:00.304020639 +0000
--- src/eval.c 2023-01-06 16:26:48.483175162 +0000
***************
*** 5676,5682 ****
case VAR_CLASS:
{
class_T *cl = tv->vval.v_class;
! if (cl != NULL && cl->class_copyID != copyID)
{
cl->class_copyID = copyID;
for (int i = 0; !abort
--- 5676,5683 ----
case VAR_CLASS:
{
class_T *cl = tv->vval.v_class;
! if (cl != NULL && cl->class_copyID != copyID
! && (cl->class_flags && CLASS_INTERFACE) == 0)
{
cl->class_copyID = copyID;
for (int i = 0; !abort
*** ../vim-9.0.1151/src/testdir/test_vim9_class.vim 2023-01-05 19:59:14.007418126 +0000
--- src/testdir/test_vim9_class.vim 2023-01-06 18:35:33.470596207 +0000
***************
*** 612,616 ****
--- 612,669 ----
v9.CheckScriptFailure(lines, 'E1345: Not a valid command in an interface: return 5')
enddef
+ def Test_class_implements_interface()
+ var lines =<< trim END
+ vim9script
+
+ interface Some
+ static count: number
+ def Method(nr: number)
+ endinterface
+
+ class SomeImpl implements Some
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static counter: number
+ def Method(nr: number)
+ endinterface
+
+ class SomeImpl implements Some
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1348: Member "counter" of interface "Some" not implemented')
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static count: number
+ def Methods(nr: number)
+ endinterface
+
+ class SomeImpl implements Some
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1349: Function "Methods" of interface "Some" not implemented')
+ enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
*** ../vim-9.0.1151/src/version.c 2023-01-05 20:14:39.263710543 +0000
--- src/version.c 2023-01-06 18:21:33.688692756 +0000
***************
*** 697,698 ****
--- 697,700 ----
{ /* Add new patch number below this line */
+ /**/
+ 1152,
/**/
--
CART DRIVER: Bring out your dead!
There are legs stick out of windows and doors. Two MEN are fighting in the
mud - covered from head to foot in it. Another MAN is on his hands in
knees shovelling mud into his mouth. We just catch sight of a MAN falling
into a well.
"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD
/// 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 ///