# HG changeset patch # User Bram Moolenaar # Date 1672948806 -3600 # Node ID 5c1b7a87466e982316a96f2e7993ca7c64b5a3f3 # Parent 40a31588b97c2266cce6b35d3de5ab22ad358d16 patch 9.0.1150: :interface is not implemented yet Commit: https://github.com/vim/vim/commit/554d0313022c3977c71f7dcbc5c841ef43d988a6 Author: Bram Moolenaar Date: Thu Jan 5 19:59:18 2023 +0000 patch 9.0.1150: :interface is not implemented yet Problem: :interface is not implemented yet. Solution: Implement the basics of :interface. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3346,8 +3346,8 @@ EXTERN char e_not_allowed_to_add_or_remo #ifdef FEAT_EVAL EXTERN char e_class_name_must_start_with_uppercase_letter_str[] INIT(= N_("E1314: Class name must start with an uppercase letter: %s")); -EXTERN char e_white_space_required_after_class_name_str[] - INIT(= N_("E1315: White space required after class name: %s")); +EXTERN char e_white_space_required_after_name_str[] + INIT(= N_("E1315: White space required after name: %s")); EXTERN char e_class_can_only_be_defined_in_vim9_script[] INIT(= N_("E1316: Class can only be defined in Vim9 script")); EXTERN char e_invalid_object_member_declaration_str[] @@ -3406,4 +3406,12 @@ EXTERN char e_argument_already_declared_ INIT(= N_("E1340: Argument already declared in the class: %s")); EXTERN char e_variable_already_declared_in_class_str[] INIT(= N_("E1341: Variable already declared in the class: %s")); -#endif +EXTERN char e_interface_can_only_be_defined_in_vim9_script[] + INIT(= N_("E1342: Interface can only be defined in Vim9 script")); +EXTERN char e_interface_name_must_start_with_uppercase_letter_str[] + INIT(= N_("E1343: Interface name must start with an uppercase letter: %s")); +EXTERN char e_cannot_initialize_member_in_interface[] + 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 diff --git a/src/ex_cmds.h b/src/ex_cmds.h --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -756,7 +756,7 @@ EXCMD(CMD_inoremenu, "inoremenu", ex_men EXCMD(CMD_intro, "intro", ex_intro, EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), -EXCMD(CMD_interface, "interface", ex_interface, +EXCMD(CMD_interface, "interface", ex_class, EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_isearch, "isearch", ex_findpat, diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -288,7 +288,6 @@ static void ex_tag_cmd(exarg_T *eap, cha # define ex_execute ex_ni # define ex_finally ex_ni # define ex_incdec ex_ni -# define ex_interface ex_ni # define ex_finish ex_ni # define ex_function ex_ni # define ex_if ex_ni diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -46,7 +46,7 @@ char_u *get_scriptlocal_funcname(char_u char_u *alloc_printable_func_name(char_u *fname); char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi); void list_functions(regmatch_T *regmatch); -ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int in_class); +ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, int class_flags); void ex_function(exarg_T *eap); ufunc_T *find_func_by_name(char_u *name, compiletype_T *compile_type); void ex_defcompile(exarg_T *eap); diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -1,7 +1,6 @@ /* vim9class.c */ void ex_class(exarg_T *eap); type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx); -void ex_interface(exarg_T *eap); void ex_enum(exarg_T *eap); void ex_type(exarg_T *eap); int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); 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 @@ -552,5 +552,65 @@ def Test_class_object_to_string() v9.CheckScriptSuccess(lines) enddef +def Test_interface_basics() + var lines =<< trim END + vim9script + interface Something + this.value: string + static count: number + def GetCount(): number + endinterface + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + interface SomethingWrong + static count = 7 + endinterface + END + v9.CheckScriptFailure(lines, 'E1342:') + + lines =<< trim END + vim9script + + interface Some + static count: number + def Method(count: number) + endinterface + END + # TODO: this should give an error for "count" shadowing + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + interface somethingWrong + static count = 7 + endinterface + END + v9.CheckScriptFailure(lines, 'E1343: Interface name must start with an uppercase letter: somethingWrong') + + lines =<< trim END + vim9script + interface SomethingWrong + this.value: string + static count = 7 + def GetCount(): number + endinterface + END + v9.CheckScriptFailure(lines, 'E1344:') + + lines =<< trim END + vim9script + interface SomethingWrong + this.value: string + static count: number + def GetCount(): number + return 5 + enddef + endinterface + END + v9.CheckScriptFailure(lines, 'E1345: Not a valid command in an interface: return 5') +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -215,7 +215,7 @@ get_function_args( garray_T *default_args, int skip, exarg_T *eap, // can be NULL - int in_class, // TRUE when inside a class + int in_class, // non-zero when inside a class or interface garray_T *newlines, // function body lines garray_T *lines_to_free) { @@ -4462,7 +4462,9 @@ list_functions(regmatch_T *regmatch) * When "name_arg" is not NULL this is a nested function, using "name_arg" for * the function name. * "lines_to_free" is a list of strings to be freed later. - * If "in_class" is TRUE then the function is defined inside a class. + * If "class_flags" has CF_CLASS then the function is defined inside a class. + * With CF_INTERFACE the function is define inside an interface, only the + * ":def"/":function" line is expected, no function body. * Returns a pointer to the function or NULL if no function defined. */ ufunc_T * @@ -4470,7 +4472,7 @@ define_function( exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, - int in_class) + int class_flags) { int j; int c; @@ -4545,7 +4547,7 @@ define_function( /* * Get the function name. There are these situations: - * func normal function name, also when "in_class" is TRUE + * func normal function name, also when "class_flags" is non-zero * "name" == func, "fudi.fd_dict" == NULL * dict.func new dictionary entry * "name" == NULL, "fudi.fd_dict" set, @@ -4586,7 +4588,7 @@ define_function( } int tfn_flags = TFN_NO_AUTOLOAD | TFN_NEW_FUNC - | (in_class ? TFN_IN_CLASS : 0); + | (class_flags != 0 ? TFN_IN_CLASS : 0); name = save_function_name(&p, &is_global, eap->skip, tfn_flags, &fudi); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) @@ -4789,7 +4791,7 @@ define_function( if (get_function_args(&p, ')', &newargs, eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE, NULL, &varargs, &default_args, eap->skip, - eap, in_class, &newlines, lines_to_free) == FAIL) + eap, class_flags, &newlines, lines_to_free) == FAIL) goto errret_2; whitep = p; @@ -4899,7 +4901,9 @@ define_function( // Do not define the function when getting the body fails and when // skipping. - if (get_function_body(eap, &newlines, line_arg, lines_to_free) == FAIL + if (((class_flags & CF_INTERFACE) == 0 + && get_function_body(eap, &newlines, line_arg, lines_to_free) + == FAIL) || eap->skip) goto erret; @@ -4934,7 +4938,7 @@ define_function( if (name == NULL) goto erret; } - else if (!in_class) + else if (class_flags == 0) { hashtab_T *ht; char_u *find_name = name; @@ -5159,7 +5163,7 @@ define_function( hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); } - else if (!in_class && hash_add(&func_hashtab, + else if (class_flags == 0 && hash_add(&func_hashtab, UF2HIKEY(fp), "add function") == FAIL) { free_fp = TRUE; @@ -5251,7 +5255,7 @@ ex_function(exarg_T *eap) garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); - (void)define_function(eap, NULL, &lines_to_free, FALSE); + (void)define_function(eap, NULL, &lines_to_free, 0); ga_clear_strings(&lines_to_free); } 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 */ /**/ + 1150, +/**/ 1149, /**/ 1148, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2854,4 +2854,8 @@ long elapsed(DWORD start_tick); #define MAX_LSHIFT_BITS (varnumber_T)((sizeof(uvarnumber_T) * 8) - 1) +// Flags used by "class_flags" of define_function() +#define CF_CLASS 1 // inside a class +#define CF_INTERFACE 2 // inside an interface + #endif // VIM__H diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -26,7 +26,7 @@ * Returns OK or FAIL. When OK then "varname_end" is set to just after the * variable name and "type_ret" is set to the decleared or detected type. * "init_expr" is set to the initialisation expression (allocated), if there is - * one. + * one. For an interface "init_expr" is NULL. */ static int parse_member( @@ -119,7 +119,14 @@ parse_member( *type_ret = type; if (expr_end > expr_start) + { + if (init_expr == NULL) + { + emsg(_(e_cannot_initialize_member_in_interface)); + return FAIL; + } *init_expr = vim_strnsave(expr_start, expr_end - expr_start); + } return OK; } @@ -175,15 +182,21 @@ add_members_to_class( /* * Handle ":class" and ":abstract class" up to ":endclass". + * Handle ":interface" up to ":endinterface". */ void ex_class(exarg_T *eap) { + int is_class = eap->cmdidx == CMD_class; // FALSE for :interface + if (!current_script_is_vim9() || (cmdmod.cmod_flags & CMOD_LEGACY) || !getline_equal(eap->getline, eap->cookie, getsourceline)) { - emsg(_(e_class_can_only_be_defined_in_vim9_script)); + if (is_class) + emsg(_(e_class_can_only_be_defined_in_vim9_script)); + else + emsg(_(e_interface_can_only_be_defined_in_vim9_script)); return; } @@ -201,13 +214,17 @@ ex_class(exarg_T *eap) if (!ASCII_ISUPPER(*arg)) { - semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg); + if (is_class) + semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg); + else + semsg(_(e_interface_name_must_start_with_uppercase_letter_str), + arg); return; } char_u *name_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START); if (!IS_WHITE_OR_NUL(*name_end)) { - semsg(_(e_white_space_required_after_class_name_str), arg); + semsg(_(e_white_space_required_after_name_str), arg); return; } @@ -239,7 +256,8 @@ ex_class(exarg_T *eap) ga_init2(&objmethods, sizeof(ufunc_T *), 10); /* - * Go over the body of the class until "endclass" is found. + * Go over the body of the class/interface until "endclass" or + * "endinterface" is found. */ char_u *theline = NULL; int success = FALSE; @@ -262,9 +280,10 @@ ex_class(exarg_T *eap) } char_u *p = line; - if (checkforcmd(&p, "endclass", 4)) + char *end_name = is_class ? "endclass" : "endinterface"; + if (checkforcmd(&p, end_name, is_class ? 4 : 5)) { - if (STRNCMP(line, "endclass", 8) != 0) + if (STRNCMP(line, end_name, is_class ? 8 : 12) != 0) semsg(_(e_command_cannot_be_shortened_str), line); else if (*p == '|' || !ends_excmd2(line, p)) semsg(_(e_trailing_characters_str), p); @@ -272,6 +291,12 @@ ex_class(exarg_T *eap) success = TRUE; break; } + char *wrong_name = is_class ? "endinterface" : "endclass"; + if (checkforcmd(&p, wrong_name, is_class ? 5 : 4)) + { + semsg(_(e_invalid_command_str), line); + break; + } int has_public = FALSE; if (checkforcmd(&p, "public", 3)) @@ -320,7 +345,8 @@ ex_class(exarg_T *eap) type_T *type = NULL; char_u *init_expr = NULL; if (parse_member(eap, line, varname, has_public, - &varname_end, &type_list, &type, &init_expr) == FAIL) + &varname_end, &type_list, &type, + is_class ? &init_expr: NULL) == FAIL) break; if (add_member(&objmembers, varname, varname_end, has_public, type, init_expr) == FAIL) @@ -358,7 +384,8 @@ ex_class(exarg_T *eap) ea.cookie = eap->cookie; ga_init2(&lines_to_free, sizeof(char_u *), 50); - ufunc_T *uf = define_function(&ea, NULL, &lines_to_free, TRUE); + ufunc_T *uf = define_function(&ea, NULL, &lines_to_free, + is_class ? CF_CLASS : CF_INTERFACE); ga_clear_strings(&lines_to_free); if (uf != NULL) @@ -389,7 +416,8 @@ ex_class(exarg_T *eap) type_T *type = NULL; char_u *init_expr = NULL; if (parse_member(eap, line, varname, has_public, - &varname_end, &type_list, &type, &init_expr) == FAIL) + &varname_end, &type_list, &type, + is_class ? &init_expr : NULL) == FAIL) break; if (add_member(&classmembers, varname, varname_end, has_public, type, init_expr) == FAIL) @@ -401,7 +429,10 @@ ex_class(exarg_T *eap) else { - semsg(_(e_not_valid_command_in_class_str), line); + if (is_class) + semsg(_(e_not_valid_command_in_class_str), line); + else + semsg(_(e_not_valid_command_in_interface_str), line); break; } } @@ -415,6 +446,9 @@ ex_class(exarg_T *eap) cl = ALLOC_CLEAR_ONE(class_T); if (cl == NULL) goto cleanup; + if (!is_class) + cl->class_flags = CLASS_INTERFACE; + cl->class_refcount = 1; cl->class_name = vim_strnsave(arg, name_end - arg); if (cl->class_name == NULL) @@ -429,7 +463,7 @@ ex_class(exarg_T *eap) &cl->class_obj_member_count) == FAIL) goto cleanup; - if (cl->class_class_member_count > 0) + if (is_class && cl->class_class_member_count > 0) { // Allocate a typval for each class member and initialize it. cl->class_members_tv = ALLOC_CLEAR_MULT(typval_T, @@ -491,7 +525,8 @@ ex_class(exarg_T *eap) garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); - ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, TRUE); + ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, + is_class ? CF_CLASS : CF_INTERFACE); ga_clear_strings(&lines_to_free); vim_free(fga.ga_data); @@ -634,15 +669,6 @@ class_member_type( } /* - * Handle ":interface" up to ":endinterface". - */ - void -ex_interface(exarg_T *eap UNUSED) -{ - // TODO -} - -/* * Handle ":enum" up to ":endenum". */ void diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -991,7 +991,7 @@ compile_nested_function(exarg_T *eap, cc int save_KeyTyped = KeyTyped; KeyTyped = FALSE; - ufunc = define_function(eap, lambda_name, lines_to_free, FALSE); + ufunc = define_function(eap, lambda_name, lines_to_free, 0); KeyTyped = save_KeyTyped; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -4280,7 +4280,7 @@ exec_instructions(ectx_T *ectx) CLEAR_FIELD(ea); ea.cmd = ea.arg = iptr->isn_arg.string; ga_init2(&lines_to_free, sizeof(char_u *), 50); - define_function(&ea, NULL, &lines_to_free, FALSE); + define_function(&ea, NULL, &lines_to_free, 0); ga_clear_strings(&lines_to_free); } break;