changeset 31639:62237ea155d9 v9.0.1152

patch 9.0.1152: class "implements" argument not implemented Commit: https://github.com/vim/vim/commit/94674f2223aafeaa4690f25e12f3ebe07814c5ba Author: Bram Moolenaar <Bram@vim.org> 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.
author Bram Moolenaar <Bram@vim.org>
date Fri, 06 Jan 2023 19:45:05 +0100
parents 75be0502f95c
children 1e82a58f7cfa
files src/alloc.c src/errors.h src/eval.c src/evalvars.c src/structs.h src/testdir/test_vim9_class.vim src/version.c src/vim9class.c
diffstat 8 files changed, 250 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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
--- 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
--- 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;
 }
--- 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
--- 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
--- 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,
--- 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: <Tkey, Tentry>
-    //    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];