To: vim_dev@googlegroups.com Subject: Patch 9.0.1152 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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: - // 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: // 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 -- Bram@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 ///