changeset 31635:5c1b7a87466e v9.0.1150

patch 9.0.1150: :interface is not implemented yet Commit: https://github.com/vim/vim/commit/554d0313022c3977c71f7dcbc5c841ef43d988a6 Author: Bram Moolenaar <Bram@vim.org> 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.
author Bram Moolenaar <Bram@vim.org>
date Thu, 05 Jan 2023 21:00:06 +0100
parents 40a31588b97c
children 9ce2b35fed55
files src/errors.h src/ex_cmds.h src/ex_docmd.c src/proto/userfunc.pro src/proto/vim9class.pro src/testdir/test_vim9_class.vim src/userfunc.c src/version.c src/vim.h src/vim9class.c src/vim9compile.c src/vim9execute.c
diffstat 12 files changed, 143 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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,
--- 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
--- 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);
--- 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);
--- 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
--- 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);
 }
 
--- 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,
--- 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
--- 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
--- 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;
 
--- 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;