# HG changeset patch # User Bram Moolenaar # Date 1673030705 -3600 # Node ID 62237ea155d9484034b52e4371ff729b5e76d616 # Parent 75be0502f95c6c7c51ee4f50dca4d634c4b06042 patch 9.0.1152: class "implements" argument not implemented Commit: https://github.com/vim/vim/commit/94674f2223aafeaa4690f25e12f3ebe07814c5ba Author: Bram Moolenaar Date: Fri Jan 6 18:42:20 2023 +0000 patch 9.0.1152: class "implements" argument not implemented Problem: Class "implements" argument not implemented. Solution: Implement "implements" argument. Add basic checks for when a class implements an interface. diff --git a/src/alloc.c b/src/alloc.c --- a/src/alloc.c +++ b/src/alloc.c @@ -813,7 +813,7 @@ ga_copy_string(garray_T *gap, char_u *p) /* * Add string "p" to "gap". - * When out of memory "p" is freed and FAIL is returned. + * When out of memory FAIL is returned (caller may want to free "p"). */ int ga_add_string(garray_T *gap, char_u *p) diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3414,4 +3414,12 @@ EXTERN char e_cannot_initialize_member_i 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")); -#endif +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 diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -5676,7 +5676,8 @@ set_ref_in_item( case VAR_CLASS: { class_T *cl = tv->vval.v_class; - if (cl != NULL && cl->class_copyID != copyID) + if (cl != NULL && cl->class_copyID != copyID + && (cl->class_flags && CLASS_INTERFACE) == 0) { cl->class_copyID = copyID; for (int i = 0; !abort diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2913,7 +2913,7 @@ set_cmdarg(exarg_T *eap, char_u *oldarg) int eval_variable( char_u *name, - int len, // length of "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,12 +2923,15 @@ eval_variable( typval_T *tv = NULL; int found = FALSE; hashtab_T *ht = NULL; - int cc; + int cc = 0; type_T *type = NULL; - // truncate the name, so that we can use strcmp() - cc = name[len]; - name[len] = NUL; + 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,7 +3098,8 @@ eval_variable( } } - name[len] = cc; + if (len > 0) + name[len] = cc; return ret; } diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1494,6 +1494,10 @@ struct class_S 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 diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -612,5 +612,58 @@ def Test_interface_basics() 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 diff --git a/src/version.c b/src/version.c --- 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 */ /**/ + 1152, +/**/ 1151, /**/ 1150, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -227,15 +227,50 @@ ex_class(exarg_T *eap) semsg(_(e_white_space_required_after_name_str), arg); return; } + char_u *name_start = arg; // TODO: // generics: - // extends SomeClass - // implements SomeInterface - // specifies SomeInterface - // check that nothing follows // 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,6 +473,114 @@ ex_class(exarg_T *eap) } 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,10 +593,23 @@ ex_class(exarg_T *eap) cl->class_flags = CLASS_INTERFACE; cl->class_refcount = 1; - cl->class_name = vim_strnsave(arg, name_end - arg); + 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,7 +655,7 @@ ex_class(exarg_T *eap) have_new = TRUE; break; } - if (!have_new) + if (is_class && !have_new) { // No new() method was defined, add the default constructor. garray_T fga; @@ -589,6 +745,7 @@ ex_class(exarg_T *eap) // - 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,6 +764,8 @@ cleanup: vim_free(cl); } + ga_clear_strings(&ga_impl); + for (int round = 1; round <= 2; ++round) { garray_T *gap = round == 1 ? &classmembers : &objmembers; @@ -986,6 +1145,10 @@ class_unref(class_T *cl) // 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];